diff options
author | Håkon Hallingstad <hakon@verizonmedia.com> | 2019-11-01 19:07:56 +0100 |
---|---|---|
committer | Håkon Hallingstad <hakon@verizonmedia.com> | 2019-11-01 19:07:56 +0100 |
commit | 400650c0998fb961d1a653941d928231195261f2 (patch) | |
tree | f0a848cc009b907b85aba0a6fd97c6029a5fd97b /node-admin | |
parent | d594135ee65be1ba5de2e737f097c38528b3f3f1 (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')
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 |