From 613464e11b5c9b478873014ccb1d629bd1ae91fe Mon Sep 17 00:00:00 2001 From: HÃ¥kon Hallingstad Date: Mon, 31 Oct 2022 21:35:02 +0100 Subject: New cores client in node-admin --- .../admin/configserver/ConfigServerClients.java | 4 + .../admin/configserver/ConfigServerException.java | 10 +++ .../configserver/RealConfigServerClients.java | 9 ++ .../configserver/StandardConfigServerResponse.java | 22 +++++ .../admin/configserver/cores/CoreDumpMetadata.java | 60 +++++++++++++ .../node/admin/configserver/cores/Cores.java | 16 ++++ .../node/admin/configserver/cores/CoresImpl.java | 37 ++++++++ .../cores/bindings/ReportCoreDumpRequest.java | 27 ++++++ .../noderepository/RealNodeRepository.java | 32 +++---- .../bindings/NodeMessageResponse.java | 18 ---- .../vespa/hosted/node/admin/package-info.java | 5 -- .../node/admin/configserver/cores/CoresTest.java | 100 +++++++++++++++++++++ 12 files changed, 297 insertions(+), 43 deletions(-) create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerException.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/StandardConfigServerResponse.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/Cores.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresImpl.java create mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java delete mode 100644 node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/package-info.java create mode 100644 node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java index 4a9f761a72f..1bf4120c071 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerClients.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.node.admin.configserver; import com.yahoo.vespa.flags.FlagRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.cores.Cores; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator; import com.yahoo.vespa.hosted.node.admin.configserver.state.State; @@ -24,5 +25,8 @@ public interface ConfigServerClients { /** Get handle to the /flags/v1 REST API */ FlagRepository flagRepository(); + /** Get handle to the /cores/v1 REST API */ + Cores cores(); + void stop(); } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerException.java new file mode 100644 index 00000000000..369a1c93dc4 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/ConfigServerException.java @@ -0,0 +1,10 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver; + +/** + * @author hakonhall + */ +public class ConfigServerException extends RuntimeException { + public ConfigServerException(String message) { super(message); } + public ConfigServerException(String message, Throwable cause) { super(message, cause); } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java index 6c650700432..4311a31816a 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java @@ -2,6 +2,8 @@ package com.yahoo.vespa.hosted.node.admin.configserver; import com.yahoo.vespa.flags.FlagRepository; +import com.yahoo.vespa.hosted.node.admin.configserver.cores.Cores; +import com.yahoo.vespa.hosted.node.admin.configserver.cores.CoresImpl; import com.yahoo.vespa.hosted.node.admin.configserver.flags.RealFlagRepository; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.RealNodeRepository; @@ -22,6 +24,7 @@ public class RealConfigServerClients implements ConfigServerClients { private final Orchestrator orchestrator; private final State state; private final RealFlagRepository flagRepository; + private final Cores cores; /** * @param configServerApi the backend API to use - will be closed at {@link #stop()}. @@ -32,6 +35,7 @@ public class RealConfigServerClients implements ConfigServerClients { orchestrator = new OrchestratorImpl(configServerApi); state = new StateImpl(configServerApi); flagRepository = new RealFlagRepository(configServerApi); + cores = new CoresImpl(configServerApi); } @Override @@ -54,6 +58,11 @@ public class RealConfigServerClients implements ConfigServerClients { return flagRepository; } + @Override + public Cores cores() { + return cores; + } + @Override public void stop() { configServerApi.close(); diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/StandardConfigServerResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/StandardConfigServerResponse.java new file mode 100644 index 00000000000..0aa4af3fe22 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/StandardConfigServerResponse.java @@ -0,0 +1,22 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Strings; + +/** + * @author hakonhall + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class StandardConfigServerResponse { + @JsonProperty("message") public String message; + @JsonProperty("error-code") public String errorCode; + + public void throwOnError(String detail) { + if (!Strings.isNullOrEmpty(errorCode)) + throw new ConfigServerException(detail + ": " + message + " " + errorCode); + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java new file mode 100644 index 00000000000..8ea39e37489 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java @@ -0,0 +1,60 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver.cores; + +import com.yahoo.component.Version; +import com.yahoo.config.provision.DockerImage; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +/** + * @author hakonhall + */ +public class CoreDumpMetadata { + private String binPath; + private List backtrace; + private List backtraceAllThreads; + private Path coreDumpPath; + private String kernelVersion; + private String cpuMicrocodeVersion; + private DockerImage dockerImage; + private String vespaVersion; + + public CoreDumpMetadata() {} + + public Optional binPath() { return Optional.ofNullable(binPath); }; + public Optional> backtrace() { return Optional.ofNullable(backtrace); }; + public Optional> backtraceAllThreads() { return Optional.ofNullable(backtraceAllThreads); }; + public Optional coredumpPath() { return Optional.ofNullable(coreDumpPath); }; + public Optional kernelVersion() { return Optional.ofNullable(kernelVersion); }; + public Optional cpuMicrocodeVersion() { return Optional.ofNullable(cpuMicrocodeVersion); }; + public Optional dockerImage() { return Optional.ofNullable(dockerImage); }; + public Optional vespaVersion() { return Optional.ofNullable(vespaVersion); }; + + public CoreDumpMetadata setBinPath(String binPath) { this.binPath = binPath; return this; }; + public CoreDumpMetadata setBacktrace(List backtrace) { this.backtrace = backtrace; return this; }; + public CoreDumpMetadata setBacktraceAllThreads(List backtraceAllThreads) { this.backtraceAllThreads = backtraceAllThreads; return this; }; + public CoreDumpMetadata setCoreDumpPath(Path coreDumpPath) { this.coreDumpPath = coreDumpPath; return this; }; + public CoreDumpMetadata setKernelVersion(String kernelVersion) { this.kernelVersion = kernelVersion; return this; }; + public CoreDumpMetadata setCpuMicrocodeVersion(String cpuMicrocodeVersion) { this.cpuMicrocodeVersion = cpuMicrocodeVersion; return this; }; + public CoreDumpMetadata setDockerImage(DockerImage dockerImage) { this.dockerImage = dockerImage; return this; }; + public CoreDumpMetadata setVespaVersion(String vespaVersion) { this.vespaVersion = vespaVersion; return this; }; + + @Override + public String toString() { + return "CoreDumpMetadata{" + + "binPath=" + binPath + + ", backtrace=" + backtrace + + ", backtraceAllThreads=" + backtraceAllThreads + + ", coreDumpPath=" + coreDumpPath + + ", kernelVersion='" + kernelVersion + '\'' + + ", cpuMicrocodeVersion='" + cpuMicrocodeVersion + '\'' + + ", dockerImage=" + dockerImage + + ", vespaVersion=" + vespaVersion + + '}'; + } + + @Override public boolean equals(Object o) { throw new UnsupportedOperationException(); } + @Override public int hashCode() { throw new UnsupportedOperationException(); } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/Cores.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/Cores.java new file mode 100644 index 00000000000..6f72e4e9551 --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/Cores.java @@ -0,0 +1,16 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver.cores; + +import com.yahoo.config.provision.HostName; + +/** + * @author hakonhall + */ +public interface Cores { + /** + * @param hostname Hostname of the node that produced the core. + * @param id The ID (aka UUID aka docid) of the core. + * @param metadata Core dump metadata. + */ + void report(HostName hostname, String id, CoreDumpMetadata metadata); +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresImpl.java new file mode 100644 index 00000000000..f6393d3726c --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresImpl.java @@ -0,0 +1,37 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver.cores; + +import com.yahoo.config.provision.HostName; +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; +import com.yahoo.vespa.hosted.node.admin.configserver.StandardConfigServerResponse; +import com.yahoo.vespa.hosted.node.admin.configserver.cores.bindings.ReportCoreDumpRequest; + +import java.util.List; + +/** + * @author hakonhall + */ +public class CoresImpl implements Cores { + private final ConfigServerApi configServerApi; + + public CoresImpl(ConfigServerApi configServerApi) { + this.configServerApi = configServerApi; + } + + @Override + public void report(HostName hostname, String id, CoreDumpMetadata metadata) { + var request = new ReportCoreDumpRequest(); + metadata.binPath().ifPresent(binPath -> request.bin_path = binPath); + metadata.backtrace().ifPresent(backtrace -> request.backtrace = List.copyOf(backtrace)); + metadata.backtraceAllThreads().ifPresent(backtraceAllThreads -> request.backtrace_all_threads = List.copyOf(backtraceAllThreads)); + metadata.coredumpPath().ifPresent(coredumpPath -> request.coredump_path = coredumpPath.toString()); + metadata.kernelVersion().ifPresent(kernelVersion -> request.kernel_version = kernelVersion); + metadata.cpuMicrocodeVersion().ifPresent(cpuMicrocodeVersion -> request.cpu_microcode_version = cpuMicrocodeVersion); + metadata.dockerImage().ifPresent(dockerImage -> request.docker_image = dockerImage.asString()); + metadata.vespaVersion().ifPresent(vespaVersion -> request.vespa_version = vespaVersion); + + String uriPath = "/cores/v1/report/" + hostname.value() + "/" + id; + configServerApi.post(uriPath, request, StandardConfigServerResponse.class) + .throwOnError("Failed to report core dump at " + metadata.coredumpPath()); + } +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java new file mode 100644 index 00000000000..f31c1e71bae --- /dev/null +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java @@ -0,0 +1,27 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver.cores.bindings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; + +/** + * Jackson class of JSON request, with names of fields verified in unit test. + * + * @author hakonhall + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ReportCoreDumpRequest { + public List backtrace; + public List backtrace_all_threads; + public String bin_path; + public String coredump_path; + public String cpu_microcode_version; + public String docker_image; + public String kernel_version; + public String vespa_version; + + public ReportCoreDumpRequest() {} +} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java index b70ce491c73..a4a047b524c 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/RealNodeRepository.java @@ -2,7 +2,6 @@ package com.yahoo.vespa.hosted.node.admin.configserver.noderepository; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.base.Strings; import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.DockerImage; @@ -11,9 +10,9 @@ import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.host.FlavorOverrides; import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; import com.yahoo.vespa.hosted.node.admin.configserver.HttpException; +import com.yahoo.vespa.hosted.node.admin.configserver.StandardConfigServerResponse; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.GetAclResponse; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.GetNodesResponse; -import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.NodeMessageResponse; import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings.NodeRepositoryNode; import java.net.URI; @@ -48,9 +47,8 @@ public class RealNodeRepository implements NodeRepository { .map(RealNodeRepository::nodeRepositoryNodeFromAddNode) .collect(Collectors.toList()); - NodeMessageResponse response = configServerApi.post("/nodes/v2/node", nodesToPost, NodeMessageResponse.class); - if (Strings.isNullOrEmpty(response.errorCode)) return; - throw new NodeRepositoryException("Failed to add nodes: " + response.message + " " + response.errorCode); + configServerApi.post("/nodes/v2/node", nodesToPost, StandardConfigServerResponse.class) + .throwOnError("Failed to add nodes"); } @Override @@ -119,26 +117,20 @@ public class RealNodeRepository implements NodeRepository { @Override public void updateNodeAttributes(String hostName, NodeAttributes nodeAttributes) { - NodeMessageResponse response = configServerApi.patch( - "/nodes/v2/node/" + hostName, - nodeRepositoryNodeFromNodeAttributes(nodeAttributes), - NodeMessageResponse.class); - - if (Strings.isNullOrEmpty(response.errorCode)) return; - throw new NodeRepositoryException("Failed to update node attributes: " + response.message + " " + response.errorCode); + configServerApi.patch("/nodes/v2/node/" + hostName, + nodeRepositoryNodeFromNodeAttributes(nodeAttributes), + StandardConfigServerResponse.class) + .throwOnError("Failed to update node attributes"); } @Override public void setNodeState(String hostName, NodeState nodeState) { String state = nodeState.name(); - NodeMessageResponse response = configServerApi.put( - "/nodes/v2/state/" + state + "/" + hostName, - Optional.empty(), /* body */ - NodeMessageResponse.class); + StandardConfigServerResponse response = configServerApi.put("/nodes/v2/state/" + state + "/" + hostName, + Optional.empty(), /* body */ + StandardConfigServerResponse.class); logger.info(response.message); - - if (Strings.isNullOrEmpty(response.errorCode)) return; - throw new NodeRepositoryException("Failed to set node state: " + response.message + " " + response.errorCode); + response.throwOnError("Failed to set node state"); } private static NodeSpec createNodeSpec(NodeRepositoryNode node) { @@ -153,7 +145,7 @@ public class RealNodeRepository implements NodeRepository { NodeReports reports = NodeReports.fromMap(Optional.ofNullable(node.reports).orElseGet(Map::of)); List events = node.history.stream() .map(event -> new Event(event.agent, event.event, Optional.ofNullable(event.at).map(Instant::ofEpochMilli).orElse(Instant.EPOCH))) - .collect(Collectors.toUnmodifiableList()); + .toList(); List trustStore = Optional.ofNullable(node.trustStore).orElse(List.of()).stream() .map(item -> new TrustStoreItem(item.fingerprint, Instant.ofEpochMilli(item.expiry))) diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java deleted file mode 100644 index f4c6181f219..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/bindings/NodeMessageResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.node.admin.configserver.noderepository.bindings; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Response from PUT /nodes/v2/state/ call to node-repository. - * - * @author dybis - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class NodeMessageResponse { - @JsonProperty("message") - public String message; - @JsonProperty("error-code") - public String errorCode; -} diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/package-info.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/package-info.java deleted file mode 100644 index 6e529fe8d77..00000000000 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -@ExportPackage -package com.yahoo.vespa.hosted.node.admin; - -import com.yahoo.osgi.annotation.ExportPackage; diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java new file mode 100644 index 00000000000..b9ecbdc0837 --- /dev/null +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java @@ -0,0 +1,100 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.node.admin.configserver.cores; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.yahoo.config.provision.DockerImage; +import com.yahoo.config.provision.HostName; +import com.yahoo.test.json.JsonTestHelper; +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerApi; +import com.yahoo.vespa.hosted.node.admin.configserver.ConfigServerException; +import com.yahoo.vespa.hosted.node.admin.configserver.StandardConfigServerResponse; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.nio.file.Path; +import java.util.List; + +import static com.yahoo.yolean.Exceptions.uncheck; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author hakonhall + */ +class CoresTest { + private final ObjectMapper mapper = new ObjectMapper(); + private final ConfigServerApi configServerApi = mock(ConfigServerApi.class); + private final Cores cores = new CoresImpl(configServerApi); + private final HostName hostname = HostName.of("foo.com"); + private final String id = "5c987afb-347a-49ee-a0c5-bef56bbddeb0"; + private final CoreDumpMetadata metadata = new CoreDumpMetadata() + .setKernelVersion("4.18.0-372.26.1.el8_6.x86_64") + .setCpuMicrocodeVersion("0x1000065") + .setCoreDumpPath(Path.of("/data/vespa/processed-coredumps/h7641a/5c987afb-347a-49ee-a0c5-bef56bbddeb0/dump_java.core.813")) + .setDockerImage(DockerImage.fromString("us-central1-docker.pkg.dev/vespa-external-cd/vespa-cloud/vespa/cloud-tenant-rhel8:8.68.8")) + .setBinPath("/usr/bin/java") + .setVespaVersion("8.68.8") + .setBacktraceAllThreads(List.of("Attaching to core /opt/vespa/var/crash/processing/5c987afb-347a-49ee-a0c5-bef56bbddeb0/dump_java.core.813 from executable /usr/bin/java, please wait...", + "Debugger attached successfully.", + " - com.yahoo.jdisc.core.TimeoutManagerImpl$ManagerTask.run() @bci=3, line=123 (Interpreted frame)", + " - java.lang.Thread.run() @bci=11, line=833 (Interpreted frame)")) + .setBacktrace(List.of("Example", "of", "backtrace")); + + @Test + void reportOK() { + var oKResponse = new StandardConfigServerResponse(); + oKResponse.message = "OK"; + when(configServerApi.post(any(), any(), any())).thenReturn(oKResponse); + + cores.report(hostname, id, metadata); + + var pathCaptor = ArgumentCaptor.forClass(String.class); + var bodyJsonPojoCaptor = ArgumentCaptor.forClass(Object.class); + verify(configServerApi, times(1)).post(pathCaptor.capture(), bodyJsonPojoCaptor.capture(), any()); + + assertEquals("/cores/v1/report/" + hostname + "/" + id, pathCaptor.getValue()); + + assertEquals(""" + { + "backtrace": [ + "Example", + "of", + "backtrace" + ], + "backtrace_all_threads": [ + "Attaching to core /opt/vespa/var/crash/processing/5c987afb-347a-49ee-a0c5-bef56bbddeb0/dump_java.core.813 from executable /usr/bin/java, please wait...", + "Debugger attached successfully.", + " - com.yahoo.jdisc.core.TimeoutManagerImpl$ManagerTask.run() @bci=3, line=123 (Interpreted frame)", + " - java.lang.Thread.run() @bci=11, line=833 (Interpreted frame)" + ], + "bin_path": "/usr/bin/java", + "coredump_path": "/data/vespa/processed-coredumps/h7641a/5c987afb-347a-49ee-a0c5-bef56bbddeb0/dump_java.core.813", + "cpu_microcode_version": "0x1000065", + "docker_image": "us-central1-docker.pkg.dev/vespa-external-cd/vespa-cloud/vespa/cloud-tenant-rhel8:8.68.8", + "kernel_version": "4.18.0-372.26.1.el8_6.x86_64", + "vespa_version": "8.68.8" + }""", + JsonTestHelper.normalize(uncheck(() -> mapper.writeValueAsString(bodyJsonPojoCaptor.getValue())))); + } + + @Test + void reportFails() { + var response = new StandardConfigServerResponse(); + response.errorCode = "503"; + response.message = "error detail"; + when(configServerApi.post(any(), any(), any())).thenReturn(response); + + assertThrows(ConfigServerException.class, + () -> cores.report(hostname, "abcde-1234", metadata), + "Failed to report core dump at Optional[/data/vespa/processed-coredumps/h7641a/5c987afb-347a-49ee-a0c5-bef56bbddeb0/dump_java.core.813]: error detail 503"); + + var pathCaptor = ArgumentCaptor.forClass(String.class); + var bodyJsonPojoCaptor = ArgumentCaptor.forClass(Object.class); + verify(configServerApi).post(pathCaptor.capture(), bodyJsonPojoCaptor.capture(), any()); + } +} \ No newline at end of file -- cgit v1.2.3