Browse Source

Model for the new health reporting api (#83398)

This change introduces basic model for health reporting api that
includes: health status, health indicator, component and service to
report them.
Ievgen Degtiarenko 3 years ago
parent
commit
b36947ef84

+ 1 - 0
build-tools-internal/src/main/resources/changelog-schema.json

@@ -39,6 +39,7 @@
             "Features",
             "Geo",
             "Graph",
+            "Health",
             "Highlighting",
             "ILM+SLM",
             "IdentityProvider",

+ 5 - 0
docs/changelog/83398.yaml

@@ -0,0 +1,5 @@
+pr: 83398
+summary: Model for the new health reporting api
+area: Health
+type: feature
+issues: []

+ 59 - 0
server/src/main/java/org/elasticsearch/health/HealthComponentResult.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.health;
+
+import org.elasticsearch.xcontent.ToXContentObject;
+import org.elasticsearch.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.TreeMap;
+
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toList;
+
+public record HealthComponentResult(String name, HealthStatus status, List<HealthIndicatorResult> indicators) implements ToXContentObject {
+
+    public static Collection<HealthComponentResult> createComponentsFromIndicators(Collection<HealthIndicatorResult> indicators) {
+        return indicators.stream()
+            .collect(
+                groupingBy(
+                    HealthIndicatorResult::component,
+                    TreeMap::new,
+                    collectingAndThen(toList(), HealthComponentResult::createComponentFromIndicators)
+                )
+            )
+            .values();
+    }
+
+    private static HealthComponentResult createComponentFromIndicators(List<HealthIndicatorResult> indicators) {
+        assert indicators.size() > 0 : "Component should not be non empty";
+        assert indicators.stream().map(HealthIndicatorResult::component).distinct().count() == 1L
+            : "Should not mix indicators from different components";
+        return new HealthComponentResult(
+            indicators.get(0).component(),
+            HealthStatus.merge(indicators.stream().map(HealthIndicatorResult::status)),
+            indicators
+        );
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        builder.field("status", status);
+        builder.startObject("indicators");
+        for (HealthIndicatorResult indicator : indicators) {
+            builder.field(indicator.name(), indicator, params);
+        }
+        builder.endObject();
+        return builder.endObject();
+    }
+}

+ 30 - 0
server/src/main/java/org/elasticsearch/health/HealthIndicatorResult.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.health;
+
+import org.elasticsearch.xcontent.ToXContentFragment;
+import org.elasticsearch.xcontent.ToXContentObject;
+import org.elasticsearch.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+public record HealthIndicatorResult(String name, String component, HealthStatus status, String summary, ToXContentFragment details)
+    implements
+        ToXContentObject {
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        builder.field("status", status);
+        builder.field("summary", summary);
+        builder.field("details", details, params);
+        // TODO 83303: Add detail / documentation
+        return builder.endObject();
+    }
+}

+ 17 - 0
server/src/main/java/org/elasticsearch/health/HealthIndicatorService.java

@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.health;
+
+/**
+ * This is a service interface used to report health indicators from the different plugins.
+ */
+public interface HealthIndicatorService {
+
+    HealthIndicatorResult calculate();
+}

+ 32 - 0
server/src/main/java/org/elasticsearch/health/HealthStatus.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.health;
+
+import java.util.Comparator;
+import java.util.stream.Stream;
+
+public enum HealthStatus {
+    GREEN((byte) 0),
+    YELLOW((byte) 1),
+    RED((byte) 2);
+
+    private final byte value;
+
+    HealthStatus(byte value) {
+        this.value = value;
+    }
+
+    public byte value() {
+        return value;
+    }
+
+    public static HealthStatus merge(Stream<HealthStatus> statuses) {
+        return statuses.max(Comparator.comparing(HealthStatus::value)).orElse(GREEN);
+    }
+}

+ 44 - 0
server/src/test/java/org/elasticsearch/health/HealthComponentResultTests.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.health;
+
+import org.elasticsearch.test.ESTestCase;
+
+import java.util.List;
+
+import static org.elasticsearch.health.HealthStatus.GREEN;
+import static org.elasticsearch.health.HealthStatus.YELLOW;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.hasItems;
+
+public class HealthComponentResultTests extends ESTestCase {
+
+    public void testGroupIndicators() {
+
+        var indicator1 = new HealthIndicatorResult("indicator1", "component1", GREEN, null, null);
+        var indicator2 = new HealthIndicatorResult("indicator2", "component1", YELLOW, null, null);
+        var indicator3 = new HealthIndicatorResult("indicator3", "component2", GREEN, null, null);
+
+        var components = HealthComponentResult.createComponentsFromIndicators(List.of(indicator1, indicator2, indicator3));
+
+        assertThat(
+            components,
+            anyOf(
+                hasItems(
+                    new HealthComponentResult("component1", YELLOW, List.of(indicator2, indicator1)),
+                    new HealthComponentResult("component2", GREEN, List.of(indicator3))
+                ),
+                hasItems(
+                    new HealthComponentResult("component1", YELLOW, List.of(indicator1, indicator2)),
+                    new HealthComponentResult("component2", GREEN, List.of(indicator3))
+                )
+            )
+        );
+    }
+}

+ 45 - 0
server/src/test/java/org/elasticsearch/health/HealthStatusTests.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.health;
+
+import org.elasticsearch.test.ESTestCase;
+
+import java.util.ArrayList;
+import java.util.stream.Stream;
+
+import static org.elasticsearch.health.HealthStatus.GREEN;
+import static org.elasticsearch.health.HealthStatus.RED;
+import static org.elasticsearch.health.HealthStatus.YELLOW;
+
+public class HealthStatusTests extends ESTestCase {
+
+    public void testAllGreenStatuses() {
+        assertEquals(GREEN, HealthStatus.merge(randomStatusesContaining(GREEN)));
+    }
+
+    public void testYellowStatus() {
+        assertEquals(YELLOW, HealthStatus.merge(randomStatusesContaining(GREEN, YELLOW)));
+    }
+
+    public void testRedStatus() {
+        assertEquals(RED, HealthStatus.merge(randomStatusesContaining(GREEN, YELLOW, RED)));
+    }
+
+    public void testEmpty() {
+        assertEquals(GREEN, HealthStatus.merge(Stream.empty()));
+    }
+
+    private static Stream<HealthStatus> randomStatusesContaining(HealthStatus... statuses) {
+        var result = new ArrayList<HealthStatus>();
+        for (HealthStatus status : statuses) {
+            result.addAll(randomList(1, 10, () -> status));
+        }
+        return result.stream();
+    }
+}