summaryrefslogtreecommitdiffstats
path: root/node-admin
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@verizonmedia.com>2019-11-01 19:07:56 +0100
committerHåkon Hallingstad <hakon@verizonmedia.com>2019-11-01 19:07:56 +0100
commit400650c0998fb961d1a653941d928231195261f2 (patch)
treef0a848cc009b907b85aba0a6fd97c6029a5fd97b /node-admin
parentd594135ee65be1ba5de2e737f097c38528b3f3f1 (diff)
Define the ONCE node report type
Also defines a snippet generator to limit the amount of text written to node repo.
Diffstat (limited to 'node-admin')
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeReports.java22
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/BaseReport.java17
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/text/SnippetGenerator.java74
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/text/SnippetGeneratorTest.java70
4 files changed, 179 insertions, 4 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeReports.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeReports.java
index 72c7885d8c1..70ce548916a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeReports.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/NodeReports.java
@@ -3,11 +3,14 @@ package com.yahoo.vespa.hosted.node.admin.configserver.noderepository;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.reports.BaseReport;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.TreeMap;
+import java.util.stream.Collectors;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -43,6 +46,25 @@ public class NodeReports {
return Optional.ofNullable(reports.get(reportId)).map(r -> uncheck(() -> mapper.treeToValue(r, jacksonClass)));
}
+ /** Gets all reports of the given types and deserialize with the given jacksonClass. */
+ public <T> TreeMap<String, T> getReports(Class<T> jacksonClass, BaseReport.Type... types) {
+ Set<BaseReport.Type> typeSet = Set.of(types);
+
+ return reports.entrySet().stream()
+ .filter(entry -> {
+ JsonNode reportType = entry.getValue().findValue(BaseReport.TYPE_FIELD);
+ if (reportType == null || !reportType.isTextual()) return false;
+ Optional<BaseReport.Type> type = BaseReport.Type.deserialize(reportType.asText());
+ return type.map(typeSet::contains).orElse(false);
+ })
+ .collect(Collectors.toMap(
+ entry -> entry.getKey(),
+ entry -> uncheck(() -> mapper.treeToValue(entry.getValue(), jacksonClass)),
+ (x,y) -> x, // resolves key collisions - cannot happen.
+ TreeMap::new
+ ));
+ }
+
public void removeReport(String reportId) {
if (reports.containsKey(reportId)) {
reports.put(reportId, null);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/BaseReport.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/BaseReport.java
index eac5f7300ef..1da180226b2 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/BaseReport.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/BaseReport.java
@@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
+import java.util.stream.Stream;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -23,10 +24,10 @@ import static com.yahoo.yolean.Exceptions.uncheck;
* <p><strong>Subclass requirements</strong>
*
* <ol>
- * <li>A subclass mush be a Jackson class that can be mapped to {@link JsonNode} with {@link #toJsonNode()},
+ * <li>A subclass must be a Jackson class that can be mapped to {@link JsonNode} with {@link #toJsonNode()},
* and from {@link JsonNode} with {@link #fromJsonNode(JsonNode, Class)}.</li>
- * <li>A subclass must override {@link #updates(BaseReport)} and make sure to return false if
- * {@code !super.updates(current)}.</li>
+ * <li>A subclass must override {@link #updates(BaseReport)} and make sure to return true if
+ * {@code super.updates(current)}.</li>
* </ol>
*
* @author hakonhall
@@ -51,10 +52,18 @@ public class BaseReport {
public enum Type {
/** The default type if none given, or not recognized. */
UNSPECIFIED,
+ /** A program to be executed once. */
+ ONCE,
/** The host has a soft failure and should be parked for manual inspection. */
SOFT_FAIL,
/** The host has a hard failure and should be given back to siteops. */
- HARD_FAIL
+ HARD_FAIL;
+
+ public static Optional<Type> deserialize(String typeString) {
+ return Stream.of(Type.values()).filter(type -> type.name().equalsIgnoreCase(typeString)).findAny();
+ }
+
+ public String serialize() { return name(); }
}
@JsonCreator
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/text/SnippetGenerator.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/text/SnippetGenerator.java
new file mode 100644
index 00000000000..1cb75434486
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/text/SnippetGenerator.java
@@ -0,0 +1,74 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.task.util.text;
+
+/**
+ * @author hakonhall
+ */
+public class SnippetGenerator {
+
+ public static final String OMIT_PREFIX = "[...";
+ public static final String OMIT_SUFFIX = " chars omitted]";
+ /**
+ * If {@code maxLength in }{@link #makeSnippet(String, int maxLength)} is less than this limit,
+ * a snippet would actually be larger than maxLength.
+ */
+ public static final int LEAST_MAXLENGTH = OMIT_PREFIX.length() + 2 + OMIT_SUFFIX.length();
+
+ public String makeSnippet(String possiblyHugeString, int maxLength) {
+ if (possiblyHugeString.length() <= maxLength) {
+ return possiblyHugeString;
+ }
+
+ // We will preserve a prefix and suffix, but replace the middle part of possiblyHugeString
+ // with a cut text:
+ // possiblyHugeString = prefix + omitted + suffix
+ // result = prefix + cut + suffix
+ // cut = OMIT_PREFIX + size + OMIT_SUFFIX
+ // size = format("%d", omitted.length())
+ // digits = size.length()
+ int assumedDigits = 2; // Just an initial guess: size.length() between 10 and 99
+ int prefixLength, suffixLength;
+ String size;
+
+ while (true) {
+ int assumedCutLength = OMIT_PREFIX.length() + assumedDigits + OMIT_SUFFIX.length();
+ // Make prefixLength ~ suffixLength, with prefixLength >= suffixLength
+ suffixLength = Math.max((maxLength - assumedCutLength) / 2, 0);
+ prefixLength = Math.max(maxLength - assumedCutLength - suffixLength, 0);
+ // RHS is guaranteed to be >= 0
+ int omittedLength = possiblyHugeString.length() - prefixLength - suffixLength;
+ size = String.format("%d", omittedLength);
+
+ // If assumedCutLength happens to be wrong, we retry with an adjusted setting.
+ int actualDigits = size.length();
+ if (actualDigits == assumedDigits) {
+ break;
+ }
+
+ // Is this loop guaranteed to finish? Yes, because from one iteration to the next,
+ // omittedLength can change by at most 9 (size having 1 digit to size with 10 digits
+ // or vice versa):
+ // - If actualDigits < assumedDigits, omittedLength will decrease on next iteration
+ // by 1-9, and so size can at most decrease by another 1 for that iteration. And,
+ // if it did decrease by 1, it cannot decrease again (and must therefore break
+ // the loop) in the iteration after, since a drop of (at most) 18 cannot remove
+ // 2 digits from a number.
+ // - If actualDigits > assumedDigits, a similar argument holds.
+ assumedDigits = actualDigits;
+ }
+
+ String snippet =
+ possiblyHugeString.substring(0, prefixLength) +
+ OMIT_PREFIX +
+ size +
+ OMIT_SUFFIX +
+ possiblyHugeString.substring(possiblyHugeString.length() - suffixLength);
+
+ if (snippet.length() > maxLength) {
+ // This can happen if maxLength is too small.
+ return possiblyHugeString.substring(0, maxLength);
+ } else {
+ return snippet;
+ }
+ }
+}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/text/SnippetGeneratorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/text/SnippetGeneratorTest.java
new file mode 100644
index 00000000000..59ae4633dce
--- /dev/null
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/text/SnippetGeneratorTest.java
@@ -0,0 +1,70 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.task.util.text;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author hakonhall
+ */
+public class SnippetGeneratorTest {
+ private final SnippetGenerator generator = new SnippetGenerator();
+
+ private void assertSnippet(String text, int maxLength, String expectedSnippet) {
+ assertEquals(expectedSnippet, generator.makeSnippet(text, maxLength));
+ }
+
+ @Test
+ public void prefixSnippetForReallySmallMaxLength() {
+ assertSnippet(
+ "This is a long text that should be snippeted", 0,
+ "");
+
+ assertSnippet(
+ "This is a long text that should be snippeted", 1,
+ "T");
+
+ assertSnippet(
+ "This is a long text that should be snippeted", 10,
+ "This is a ");
+
+ assertSnippet(
+ "This is a long text that should be snippeted", 20,
+ "This is a long text ");
+ }
+
+ @Test
+ public void snippet() {
+ assertSnippet(
+ "This is a long text that should be snippeted", 21,
+ "[...44 chars omitted]");
+
+ assertSnippet(
+ "This is a long text that should be snippeted", 22,
+ "T[...43 chars omitted]");
+
+ assertSnippet(
+ "This is a long text that should be snippeted", 30,
+ "This [...35 chars omitted]eted");
+
+ assertSnippet(
+ "This is a long text that should be snippeted", 31,
+ "This [...34 chars omitted]peted");
+
+ assertSnippet(
+ "This is a long text that should be snippeted", 43,
+ "This is a l[...22 chars omitted]e snippeted");
+ }
+
+ @Test
+ public void noShorteningNeeded() {
+ assertSnippet(
+ "This is a long text that should be snippeted", 44,
+ "This is a long text that should be snippeted");
+
+ assertSnippet(
+ "This is a long text that should be snippeted", 50,
+ "This is a long text that should be snippeted");
+ }
+} \ No newline at end of file