From 7c798d4451e956b528deec8916736db32fbf6939 Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Wed, 18 Sep 2024 16:11:52 -0400 Subject: [PATCH] Add meta model, return rebalance conditions Signed-off-by: Michael Edgar --- .../console/api/KafkaRebalancesResource.java | 17 ++-- .../streamshub/console/api/model/Error.java | 13 ++- .../console/api/model/JsonApiDocument.java | 6 +- .../console/api/model/JsonApiMeta.java | 44 +++++++++ .../console/api/model/KafkaRebalance.java | 98 +++++++++++++------ .../console/api/model/Resource.java | 32 ++++-- .../api/service/KafkaRebalanceService.java | 8 ++ .../console/api/support/OASModelFilter.java | 1 + 8 files changed, 162 insertions(+), 57 deletions(-) create mode 100644 api/src/main/java/com/github/streamshub/console/api/model/JsonApiMeta.java diff --git a/api/src/main/java/com/github/streamshub/console/api/KafkaRebalancesResource.java b/api/src/main/java/com/github/streamshub/console/api/KafkaRebalancesResource.java index ca3fe57d0..306e4fef8 100644 --- a/api/src/main/java/com/github/streamshub/console/api/KafkaRebalancesResource.java +++ b/api/src/main/java/com/github/streamshub/console/api/KafkaRebalancesResource.java @@ -22,10 +22,8 @@ import org.eclipse.microprofile.openapi.annotations.enums.Explode; import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; -import org.eclipse.microprofile.openapi.annotations.media.Content; import org.eclipse.microprofile.openapi.annotations.media.Schema; import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; -import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody; import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.eclipse.microprofile.openapi.annotations.responses.APIResponseSchema; import org.eclipse.microprofile.openapi.annotations.tags.Tag; @@ -61,7 +59,7 @@ public class KafkaRebalancesResource { @GET @Produces(MediaType.APPLICATION_JSON) - @APIResponseSchema(KafkaRebalance.ListResponse.class) + @APIResponseSchema(KafkaRebalance.RebalanceDataList.class) @APIResponse(responseCode = "500", ref = "ServerError") @APIResponse(responseCode = "504", ref = "ServerTimeout") public Response listRebalances( @@ -90,6 +88,7 @@ public Response listRebalances( KafkaRebalance.Fields.REPLICATION_THROTTLE, KafkaRebalance.Fields.REPLICA_MOVEMENT_STRATEGIES, KafkaRebalance.Fields.OPTIMIZATION_RESULT, + KafkaRebalance.Fields.CONDITIONS, }, payload = ErrorCategory.InvalidQueryParameter.class) @Parameter( @@ -115,6 +114,7 @@ public Response listRebalances( KafkaRebalance.Fields.REPLICATION_THROTTLE, KafkaRebalance.Fields.REPLICA_MOVEMENT_STRATEGIES, KafkaRebalance.Fields.OPTIMIZATION_RESULT, + KafkaRebalance.Fields.CONDITIONS, })) List fields, @@ -136,7 +136,7 @@ public Response listRebalances( KafkaRebalance::fromCursor); var rebalanceList = rebalanceService.listRebalances(listSupport); - var responseEntity = new KafkaRebalance.ListResponse(rebalanceList, listSupport); + var responseEntity = new KafkaRebalance.RebalanceDataList(rebalanceList, listSupport); return Response.ok(responseEntity).build(); } @@ -145,7 +145,7 @@ public Response listRebalances( @PATCH @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @APIResponseSchema(responseCode = "200", value = KafkaRebalance.Singleton.class) + @APIResponseSchema(responseCode = "200", value = KafkaRebalance.RebalanceData.class) @Expression( targetName = "args", // Only check when the request body Id is present (separately checked for @NotNull) @@ -166,10 +166,7 @@ public Response patchRebalance( String rebalanceId, @Valid - @RequestBody(content = @Content( - schema = @Schema(implementation = KafkaRebalance.Singleton.class)) - ) - KafkaRebalance.Singleton rebalance) { + KafkaRebalance.RebalanceData rebalance) { requestedFields.accept(List.of( KafkaRebalance.Fields.NAME, @@ -190,7 +187,7 @@ public Response patchRebalance( KafkaRebalance.Fields.OPTIMIZATION_RESULT)); var result = rebalanceService.patchRebalance(rebalanceId, rebalance.getData()); - var responseEntity = new KafkaRebalance.Singleton(result); + var responseEntity = new KafkaRebalance.RebalanceData(result); return Response.ok(responseEntity).build(); } diff --git a/api/src/main/java/com/github/streamshub/console/api/model/Error.java b/api/src/main/java/com/github/streamshub/console/api/model/Error.java index b274aa3a8..358e92c2d 100644 --- a/api/src/main/java/com/github/streamshub/console/api/model/Error.java +++ b/api/src/main/java/com/github/streamshub/console/api/model/Error.java @@ -12,8 +12,10 @@ @JsonInclude(value = Include.NON_NULL) public class Error { - @Schema(description = "A meta object containing non-standard meta-information about the error") - Map meta; + @Schema( + description = "A meta object containing non-standard meta-information about the error", + implementation = JsonApiMeta.class) + JsonApiMeta meta; @Schema(description = """ a links object that MAY contain the following members: @@ -58,15 +60,12 @@ public Error(String title, String detail, Throwable cause) { this.cause = cause; } - public Map getMeta() { + public JsonApiMeta getMeta() { return meta; } public Error addMeta(String key, Object value) { - if (meta == null) { - meta = new LinkedHashMap<>(); - } - meta.put(key, value); + meta = JsonApiMeta.put(meta, key, value); return this; } diff --git a/api/src/main/java/com/github/streamshub/console/api/model/JsonApiDocument.java b/api/src/main/java/com/github/streamshub/console/api/model/JsonApiDocument.java index 43914ae31..0b2d9eb70 100644 --- a/api/src/main/java/com/github/streamshub/console/api/model/JsonApiDocument.java +++ b/api/src/main/java/com/github/streamshub/console/api/model/JsonApiDocument.java @@ -15,7 +15,7 @@ @JsonInclude(value = Include.NON_NULL) public abstract class JsonApiDocument { - private Map meta; + private JsonApiMeta meta; private Map links; static Map addEntry(Map map, K key, V value) { @@ -27,7 +27,7 @@ static Map addEntry(Map map, K key, V value) { } @JsonProperty - public Map meta() { + public JsonApiMeta meta() { return meta; } @@ -36,7 +36,7 @@ public Object meta(String key) { } public JsonApiDocument addMeta(String key, Object value) { - meta = addEntry(meta, key, value); + meta = JsonApiMeta.put(meta, key, value); return this; } diff --git a/api/src/main/java/com/github/streamshub/console/api/model/JsonApiMeta.java b/api/src/main/java/com/github/streamshub/console/api/model/JsonApiMeta.java new file mode 100644 index 000000000..d936aba34 --- /dev/null +++ b/api/src/main/java/com/github/streamshub/console/api/model/JsonApiMeta.java @@ -0,0 +1,44 @@ +package com.github.streamshub.console.api.model; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Schema(additionalProperties = Object.class) +public class JsonApiMeta { + + public static JsonApiMeta put(JsonApiMeta meta, String key, Object value) { + if (meta == null) { + meta = new JsonApiMeta(); + } + meta.put(key, value); + return meta; + } + + @JsonIgnore + private Map meta; + + @JsonAnyGetter + public Map get() { + return meta; + } + + public Object get(String key) { + return meta != null ? meta.get(key) : null; + } + + @JsonAnySetter + public JsonApiMeta put(String key, Object value) { + if (meta == null) { + meta = new LinkedHashMap<>(); + } + meta.put(key, value); + return this; + } + +} diff --git a/api/src/main/java/com/github/streamshub/console/api/model/KafkaRebalance.java b/api/src/main/java/com/github/streamshub/console/api/model/KafkaRebalance.java index 01393b32c..efa59583a 100644 --- a/api/src/main/java/com/github/streamshub/console/api/model/KafkaRebalance.java +++ b/api/src/main/java/com/github/streamshub/console/api/model/KafkaRebalance.java @@ -14,10 +14,13 @@ import jakarta.json.JsonValue; import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.media.SchemaProperty; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.github.streamshub.console.api.support.ComparatorBuilder; import com.github.streamshub.console.api.support.ErrorCategory; import com.github.streamshub.console.api.support.ListRequestContext; @@ -28,7 +31,12 @@ import static java.util.Comparator.comparing; import static java.util.Comparator.nullsLast; -@Schema(name = "KafkaRebalance") +@Schema( + name = "KafkaRebalance", + properties = { + @SchemaProperty(name = "type", enumeration = KafkaRebalance.TYPE), + @SchemaProperty(name = "meta", implementation = KafkaRebalance.Meta.class) + }) @Expression( value = "self.id != null", message = "resource ID is required", @@ -62,6 +70,7 @@ public static class Fields { public static final String REPLICATION_THROTTLE = "replicationThrottle"; public static final String REPLICA_MOVEMENT_STRATEGIES = "replicaMovementStrategies"; public static final String OPTIMIZATION_RESULT = "optimizationResult"; + public static final String CONDITIONS = "conditions"; static final Comparator ID_COMPARATOR = comparing(KafkaRebalance::getId, nullsLast(String::compareTo)); @@ -105,9 +114,9 @@ public static Comparator comparator(String fieldName, boolean de } } - @Schema(name = "KafkaRebalanceListResponse") - public static final class ListResponse extends DataList { - public ListResponse(List data, ListRequestContext listSupport) { + @Schema(name = "KafkaRebalanceDataList") + public static final class RebalanceDataList extends DataList { + public RebalanceDataList(List data, ListRequestContext listSupport) { super(data.stream() .map(entry -> { entry.addMeta("page", listSupport.buildPageMeta(entry::toCursor)); @@ -119,15 +128,47 @@ public ListResponse(List data, ListRequestContext { + @Schema(name = "KafkaRebalanceData") + public static final class RebalanceData extends DataSingleton { @JsonCreator - public Singleton(@JsonProperty("data") KafkaRebalance data) { + public RebalanceData(@JsonProperty("data") KafkaRebalance data) { super(data); } } + @Schema(name = "KafkaRebalanceMeta", additionalProperties = Object.class) + @JsonInclude(value = Include.NON_NULL) + static final class Meta extends JsonApiMeta { + @JsonProperty + @Schema( + description = """ + Action to be taken against the Kafka Rebalance resource. \ + Depends on the current resource state. + """, + enumeration = { "approve", "refresh", "stop" }, + writeOnly = true, + nullable = true) + @StringEnumeration( + allowedValues = { "approve", "refresh", "stop" }, + payload = ErrorCategory.InvalidResource.class, + message = "invalid rebalance action" + ) + /** + * @see io.strimzi.api.kafka.model.rebalance.KafkaRebalanceAnnotation + */ + String action; + + public String action() { + return action; + } + + public void action(String action) { + this.action = action; + } + } + @JsonFilter("fieldFilter") + @Schema(name = "KafkaRebalanceAttributes") static class Attributes { @JsonProperty @Schema(readOnly = true) @@ -194,43 +235,34 @@ static class Attributes { Map optimizationResult = new HashMap<>(0); @JsonProperty - @Schema(writeOnly = true, nullable = true) - @StringEnumeration( - allowedValues = { "approve", "refresh", "stop" }, - payload = ErrorCategory.InvalidResource.class, - message = "invalid rebalance action" - ) - /** - * @see io.strimzi.api.kafka.model.rebalance.KafkaRebalanceAnnotation - */ - String action; + List conditions; } public KafkaRebalance(String id) { - super(id, TYPE, new Attributes()); + super(id, TYPE, Meta::new, new Attributes()); } @JsonCreator - public KafkaRebalance(String id, String type, KafkaRebalance.Attributes attributes) { - super(id, type, attributes); + public KafkaRebalance(String id, String type, KafkaRebalance.Attributes attributes, Meta meta) { + super(id, type, meta, attributes); } /** - * Constructs a "cursor" Topic from the encoded string representation of the subset - * of Topic fields used to compare entities for pagination/sorting. + * Constructs a "cursor" KafkaRebalance from the encoded string representation of the subset + * of KafkaRebalance fields used to compare entities for pagination/sorting. */ public static KafkaRebalance fromCursor(JsonObject cursor) { if (cursor == null) { return null; } - KafkaRebalance cluster = new KafkaRebalance(cursor.getString("id")); + KafkaRebalance rebalance = new KafkaRebalance(cursor.getString("id")); JsonObject attr = cursor.getJsonObject("attributes"); - cluster.name(attr.getString(Fields.NAME, null)); - cluster.namespace(attr.getString(Fields.NAMESPACE, null)); - cluster.creationTimestamp(attr.getString(Fields.CREATION_TIMESTAMP, null)); + rebalance.name(attr.getString(Fields.NAME, null)); + rebalance.namespace(attr.getString(Fields.NAMESPACE, null)); + rebalance.creationTimestamp(attr.getString(Fields.CREATION_TIMESTAMP, null)); - return cluster; + return rebalance; } public String toCursor(List sortFields) { @@ -252,6 +284,10 @@ static void maybeAddAttribute(JsonObjectBuilder attrBuilder, List sortFi } } + public String action() { + return ((Meta) super.getMeta()).action(); + } + public String name() { return attributes.name; } @@ -380,11 +416,11 @@ public Map optimizationResult() { return attributes.optimizationResult; } - public String action() { - return attributes.action; + public List conditions() { + return attributes.conditions; } - public void action(String action) { - attributes.action = action; + public void conditions(List conditions) { + attributes.conditions = conditions; } } diff --git a/api/src/main/java/com/github/streamshub/console/api/model/Resource.java b/api/src/main/java/com/github/streamshub/console/api/model/Resource.java index 65a812694..4bb4b2316 100644 --- a/api/src/main/java/com/github/streamshub/console/api/model/Resource.java +++ b/api/src/main/java/com/github/streamshub/console/api/model/Resource.java @@ -1,11 +1,11 @@ package com.github.streamshub.console.api.model; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.function.Supplier; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.github.streamshub.console.api.support.ErrorCategory; @@ -21,19 +21,39 @@ public abstract class Resource { protected String id; + @NotNull(payload = ErrorCategory.InvalidResource.class) protected final String type; - protected Map meta; + + @Valid + protected JsonApiMeta meta; + @Valid @NotNull(payload = ErrorCategory.InvalidResource.class) protected final T attributes; - protected Resource(String id, String type, T attributes) { + @JsonIgnore + private final Supplier metaFactory; + + protected Resource(String id, String type, JsonApiMeta meta, T attributes) { + this.id = id; + this.type = type; + this.meta = meta; + this.metaFactory = JsonApiMeta::new; + this.attributes = attributes; + } + + protected Resource(String id, String type, Supplier metaFactory, T attributes) { this.id = id; this.type = type; + this.metaFactory = metaFactory; this.attributes = attributes; } + protected Resource(String id, String type, T attributes) { + this(id, type, JsonApiMeta::new, attributes); + } + public String getId() { return id; } @@ -46,7 +66,7 @@ public T getAttributes() { return attributes; } - public Map getMeta() { + public JsonApiMeta getMeta() { return meta; } @@ -56,7 +76,7 @@ public Object getMeta(String key) { public Resource addMeta(String key, Object value) { if (meta == null) { - meta = new LinkedHashMap<>(); + meta = metaFactory.get(); } meta.put(key, value); return this; diff --git a/api/src/main/java/com/github/streamshub/console/api/service/KafkaRebalanceService.java b/api/src/main/java/com/github/streamshub/console/api/service/KafkaRebalanceService.java index 560974b6a..5e2e601ed 100644 --- a/api/src/main/java/com/github/streamshub/console/api/service/KafkaRebalanceService.java +++ b/api/src/main/java/com/github/streamshub/console/api/service/KafkaRebalanceService.java @@ -18,6 +18,7 @@ import org.eclipse.microprofile.context.ThreadContext; import org.jboss.logging.Logger; +import com.github.streamshub.console.api.model.Condition; import com.github.streamshub.console.api.model.KafkaRebalance; import com.github.streamshub.console.api.support.KafkaContext; import com.github.streamshub.console.api.support.ListRequestContext; @@ -122,6 +123,13 @@ KafkaRebalance toKafkaRebalance(io.strimzi.api.kafka.model.rebalance.KafkaRebala rebalanceStatus.map(KafkaRebalanceStatus::getOptimizationResult) .ifPresent(rebalance.optimizationResult()::putAll); + rebalanceStatus.map(KafkaRebalanceStatus::getConditions).ifPresent(conditions -> { + rebalance.conditions(conditions.stream() + .filter(c -> Arrays.stream(KafkaRebalanceState.values()) + .noneMatch(stateValue -> stateValue.toString().equals(c.getType()))) + .map(Condition::new).toList()); + }); + rebalance.addMeta("allowedActions", state .map(KafkaRebalanceState::getValidAnnotations) .map(allowed -> allowed.stream().map(Enum::name).toList()) diff --git a/api/src/main/java/com/github/streamshub/console/api/support/OASModelFilter.java b/api/src/main/java/com/github/streamshub/console/api/support/OASModelFilter.java index 5b2bf4769..289d7133b 100644 --- a/api/src/main/java/com/github/streamshub/console/api/support/OASModelFilter.java +++ b/api/src/main/java/com/github/streamshub/console/api/support/OASModelFilter.java @@ -76,6 +76,7 @@ public Schema filterSchema(Schema schema) { schema.setEnumeration(null); } + maybeSaveReference(schema, "meta"); maybeSaveReference(schema, "attributes"); maybeSaveReference(schema, "relationships");