diff options
author | Bjørn Christian Seime <bjorn.christian@seime.no> | 2022-06-29 17:56:58 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-29 17:56:58 +0200 |
commit | 02a376fea1b1f11e45fdabfde8e96cebe3b83316 (patch) | |
tree | 80742fcbfd87516a8f81902519b6bf036ff36902 /container-search-and-docproc | |
parent | 2edc796a4aae38fb6d468a69600da9ba07254fa5 (diff) |
Revert "Bjorncs/application status handler"
Diffstat (limited to 'container-search-and-docproc')
4 files changed, 479 insertions, 0 deletions
diff --git a/container-search-and-docproc/src/main/java/com/yahoo/container/handler/observability/ApplicationStatusHandler.java b/container-search-and-docproc/src/main/java/com/yahoo/container/handler/observability/ApplicationStatusHandler.java new file mode 100644 index 00000000000..09b5a6ff85e --- /dev/null +++ b/container-search-and-docproc/src/main/java/com/yahoo/container/handler/observability/ApplicationStatusHandler.java @@ -0,0 +1,334 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.observability; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.yahoo.component.annotation.Inject; +import com.yahoo.component.AbstractComponent; +import com.yahoo.component.ComponentId; +import com.yahoo.component.Vtag; +import com.yahoo.component.chain.Chain; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.Container; +import com.yahoo.container.core.ApplicationMetadataConfig; +import com.yahoo.container.jdisc.JdiscBindingsConfig; +import com.yahoo.docproc.Call; +import com.yahoo.docproc.impl.DocprocService; +import com.yahoo.docproc.jdisc.DocumentProcessingHandler; +import com.yahoo.jdisc.handler.AbstractRequestHandler; +import com.yahoo.jdisc.handler.CompletionHandler; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.FastContentWriter; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.handler.ResponseDispatch; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.filter.RequestFilterBase; +import com.yahoo.jdisc.http.filter.ResponseFilterBase; +import com.yahoo.jdisc.service.ClientProvider; +import com.yahoo.jdisc.service.ServerProvider; +import com.yahoo.processing.Processor; +import com.yahoo.processing.execution.chain.ChainRegistry; +import com.yahoo.processing.handler.ProcessingHandler; +import com.yahoo.search.handler.SearchHandler; +import com.yahoo.search.searchchain.SearchChainRegistry; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Handler that outputs meta-info about the deployed Vespa application, and status of components and chains. + * + * @author gjoranv + * @author Einar M R Rosenvinge + */ +public class ApplicationStatusHandler extends AbstractRequestHandler { + + private static final ObjectMapper jsonMapper = new ObjectMapper(); + + private final JsonNode applicationJson; + private final JsonNode clientsJson; + private final JsonNode serversJson; + private final JsonNode requestFiltersJson; + private final JsonNode responseFiltersJson; + private final JdiscBindingsConfig bindingsConfig; + + @Inject + public ApplicationStatusHandler(ApplicationMetadataConfig metaConfig, + ApplicationUserdataConfig userConfig, + JdiscBindingsConfig bindingsConfig, + ComponentRegistry<ClientProvider> clientProviderRegistry, + ComponentRegistry<ServerProvider> serverProviderRegistry, + ComponentRegistry<RequestFilterBase> requestFilterRegistry, + ComponentRegistry<ResponseFilterBase> responseFilterRegistry) { + + applicationJson = renderApplicationConfigs(metaConfig, userConfig); + clientsJson = renderRequestHandlers(bindingsConfig, clientProviderRegistry.allComponentsById()); + serversJson = renderObjectComponents(serverProviderRegistry.allComponentsById()); + requestFiltersJson = renderObjectComponents(requestFilterRegistry.allComponentsById()); + responseFiltersJson = renderObjectComponents(responseFilterRegistry.allComponentsById()); + + this.bindingsConfig = bindingsConfig; + } + + @Override + public ContentChannel handleRequest(com.yahoo.jdisc.Request request, ResponseHandler handler) { + JsonNode json = new StatusResponse(applicationJson, clientsJson, serversJson, + requestFiltersJson, responseFiltersJson, bindingsConfig) + .render(); + + FastContentWriter writer = new FastContentWriter(new ResponseDispatch() { + @Override + protected com.yahoo.jdisc.Response newResponse() { + com.yahoo.jdisc.Response response = new com.yahoo.jdisc.Response(com.yahoo.jdisc.Response.Status.OK); + response.headers().add("Content-Type", List.of("application/json")); + return response; + } + }.connect(handler)); + + try { + writer.write(jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(json)); + } catch (JsonProcessingException e) { + throw new RuntimeException("Invalid JSON: " + e.getMessage(), e); + } + writer.close(); + + return new IgnoredContent(); + } + + static JsonNode renderApplicationConfigs(ApplicationMetadataConfig metaConfig, + ApplicationUserdataConfig userConfig) { + ObjectNode vespa = jsonMapper.createObjectNode(); + vespa.put("version", Vtag.currentVersion.toString()); + + ObjectNode meta = jsonMapper.createObjectNode(); + meta.put("name", metaConfig.name()); + meta.put("user", metaConfig.user()); + meta.put("path", metaConfig.path()); + meta.put("generation", metaConfig.generation()); + meta.put("timestamp", metaConfig.timestamp()); + meta.put("date", new Date(metaConfig.timestamp()).toString()); + meta.put("checksum", metaConfig.checksum()); + + ObjectNode user = jsonMapper.createObjectNode(); + user.put("version", userConfig.version()); + + ObjectNode application = jsonMapper.createObjectNode(); + application.set("vespa", vespa); + application.set("meta", meta); + application.set("user", user); + return application; + } + + static JsonNode renderObjectComponents(Map<ComponentId, ?> componentsById) { + ArrayNode ret = jsonMapper.createArrayNode(); + + for (Map.Entry<ComponentId, ?> componentEntry : componentsById.entrySet()) { + JsonNode jc = renderComponent(componentEntry.getValue(), componentEntry.getKey()); + ret.add(jc); + } + return ret; + } + + static JsonNode renderRequestHandlers(JdiscBindingsConfig bindingsConfig, + Map<ComponentId, ? extends RequestHandler> handlersById) { + ArrayNode ret = jsonMapper.createArrayNode(); + + for (Map.Entry<ComponentId, ? extends RequestHandler> handlerEntry : handlersById.entrySet()) { + String id = handlerEntry.getKey().stringValue(); + RequestHandler handler = handlerEntry.getValue(); + + ObjectNode handlerJson = renderComponent(handler, handlerEntry.getKey()); + addBindings(bindingsConfig, id, handlerJson); + ret.add(handlerJson); + } + return ret; + } + + private static void addBindings(JdiscBindingsConfig bindingsConfig, String id, ObjectNode handlerJson) { + List<String> serverBindings = new ArrayList<>(); + List<String> clientBindings = new ArrayList<>(); + + JdiscBindingsConfig.Handlers handlerConfig = bindingsConfig.handlers(id); + if (handlerConfig != null) { + serverBindings = handlerConfig.serverBindings(); + clientBindings = handlerConfig.clientBindings(); + } + handlerJson.set("serverBindings", renderBindings(serverBindings)); + handlerJson.set("clientBindings", renderBindings(clientBindings)); + } + + private static JsonNode renderBindings(List<String> bindings) { + ArrayNode ret = jsonMapper.createArrayNode(); + + for (String binding : bindings) + ret.add(binding); + + return ret; + } + + private static JsonNode renderAbstractComponents(List<? extends AbstractComponent> components) { + ArrayNode ret = jsonMapper.createArrayNode(); + + for (AbstractComponent c : components) { + JsonNode jc = renderComponent(c, c.getId()); + ret.add(jc); + } + return ret; + } + + private static ObjectNode renderComponent(Object component, ComponentId id) { + ObjectNode jc = jsonMapper.createObjectNode(); + jc.put("id", id.stringValue()); + addBundleInfo(jc, component); + return jc; + } + + private static void addBundleInfo(ObjectNode jsonObject, Object component) { + BundleInfo bundleInfo = bundleInfo(component); + jsonObject.put("class", bundleInfo.className); + jsonObject.put("bundle", bundleInfo.bundleName); + + } + + private static BundleInfo bundleInfo(Object component) { + try { + Bundle bundle = FrameworkUtil.getBundle(component.getClass()); + + String bundleName = bundle != null ? + bundle.getSymbolicName() + ":" + bundle.getVersion() : + "From classpath"; + return new BundleInfo(component.getClass().getName(), bundleName); + } catch (Exception | NoClassDefFoundError e) { + return new BundleInfo("Unavailable, reconfiguration in progress.", ""); + } + } + + static final class BundleInfo { + public final String className; + public final String bundleName; + BundleInfo(String className, String bundleName) { + this.className = className; + this.bundleName = bundleName; + } + } + + static final class StatusResponse { + private final JsonNode applicationJson; + private final JsonNode clientsJson; + private final JsonNode serversJson; + private final JsonNode requestFiltersJson; + private final JsonNode responseFiltersJson; + private final JdiscBindingsConfig bindingsConfig; + + StatusResponse(JsonNode applicationJson, + JsonNode clientsJson, + JsonNode serversJson, + JsonNode requestFiltersJson, + JsonNode responseFiltersJson, + JdiscBindingsConfig bindingsConfig) { + this.applicationJson = applicationJson; + this.clientsJson = clientsJson; + this.serversJson = serversJson; + this.requestFiltersJson = requestFiltersJson; + this.responseFiltersJson = responseFiltersJson; + this.bindingsConfig = bindingsConfig; + } + + public JsonNode render() { + ObjectNode root = jsonMapper.createObjectNode(); + + root.set("application", applicationJson); + root.set("abstractComponents", + renderAbstractComponents(Container.get().getComponentRegistry().allComponents())); + + root.set("handlers", + renderRequestHandlers(bindingsConfig, Container.get().getRequestHandlerRegistry().allComponentsById())); + root.set("clients", clientsJson); + root.set("servers", serversJson); + root.set("httpRequestFilters", requestFiltersJson); + root.set("httpResponseFilters", responseFiltersJson); + + root.set("searchChains", renderSearchChains(Container.get())); + root.set("docprocChains", renderDocprocChains(Container.get())); + root.set("processingChains", renderProcessingChains(Container.get())); + + return root; + } + + private static JsonNode renderSearchChains(Container container) { + for (RequestHandler h : container.getRequestHandlerRegistry().allComponents()) { + if (h instanceof SearchHandler) { + SearchChainRegistry scReg = ((SearchHandler) h).getSearchChainRegistry(); + return renderChains(scReg); + } + } + return jsonMapper.createObjectNode(); + } + + private static JsonNode renderDocprocChains(Container container) { + ObjectNode ret = jsonMapper.createObjectNode(); + for (RequestHandler h : container.getRequestHandlerRegistry().allComponents()) { + if (h instanceof DocumentProcessingHandler) { + ComponentRegistry<DocprocService> registry = ((DocumentProcessingHandler) h).getDocprocServiceRegistry(); + for (DocprocService service : registry.allComponents()) { + ret.set(service.getId().stringValue(), renderCalls(service.getCallStack().iterator())); + } + } + } + return ret; + } + + private static JsonNode renderProcessingChains(Container container) { + JsonNode ret = jsonMapper.createObjectNode(); + for (RequestHandler h : container.getRequestHandlerRegistry().allComponents()) { + if (h instanceof ProcessingHandler) { + ChainRegistry<Processor> registry = ((ProcessingHandler) h).getChainRegistry(); + return renderChains(registry); + } + } + return ret; + } + + // Note the generic param here! The key to make this work is '? extends Chain', but why? + static JsonNode renderChains(ComponentRegistry<? extends Chain<?>> chains) { + ObjectNode ret = jsonMapper.createObjectNode(); + for (Chain<?> chain : chains.allComponents()) { + ret.set(chain.getId().stringValue(), renderAbstractComponents(chain.components())); + } + return ret; + } + + private static JsonNode renderCalls(Iterator<Call> components) { + ArrayNode ret = jsonMapper.createArrayNode(); + while (components.hasNext()) { + Call c = components.next(); + JsonNode jc = renderComponent(c.getDocumentProcessor(), c.getDocumentProcessor().getId()); + ret.add(jc); + } + return ret; + } + + } + + private class IgnoredContent implements ContentChannel { + @Override + public void write(ByteBuffer buf, CompletionHandler handler) { + handler.completed(); + } + + @Override + public void close(CompletionHandler handler) { + handler.completed(); + } + } + +} diff --git a/container-search-and-docproc/src/main/java/com/yahoo/container/handler/observability/package-info.java b/container-search-and-docproc/src/main/java/com/yahoo/container/handler/observability/package-info.java new file mode 100644 index 00000000000..7f9dc3d1cd9 --- /dev/null +++ b/container-search-and-docproc/src/main/java/com/yahoo/container/handler/observability/package-info.java @@ -0,0 +1,5 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.container.handler.observability; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/container-search-and-docproc/src/main/resources/configdefinitions/container.handler.observability.application-userdata.def b/container-search-and-docproc/src/main/resources/configdefinitions/container.handler.observability.application-userdata.def new file mode 100644 index 00000000000..cde92df9ef4 --- /dev/null +++ b/container-search-and-docproc/src/main/resources/configdefinitions/container.handler.observability.application-userdata.def @@ -0,0 +1,8 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +# Contains user generated info about one deployed application +# The values in this config are set by config overrides in vespa-services + +namespace=container.handler.observability + +# The user-defined version of this application. +version string default="" diff --git a/container-search-and-docproc/src/test/java/com/yahoo/container/handler/observability/ApplicationStatusHandlerTest.java b/container-search-and-docproc/src/test/java/com/yahoo/container/handler/observability/ApplicationStatusHandlerTest.java new file mode 100644 index 00000000000..317f5fc1329 --- /dev/null +++ b/container-search-and-docproc/src/test/java/com/yahoo/container/handler/observability/ApplicationStatusHandlerTest.java @@ -0,0 +1,132 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.observability; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.chain.Chain; +import com.yahoo.container.core.ApplicationMetadataConfig; +import com.yahoo.container.jdisc.JdiscBindingsConfig; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.service.ClientProvider; +import com.yahoo.processing.Processor; +import com.yahoo.processing.Request; +import com.yahoo.processing.Response; +import com.yahoo.processing.execution.Execution; +import com.yahoo.processing.execution.chain.ChainRegistry; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.HashMap; + +import static com.yahoo.container.jdisc.JdiscBindingsConfig.Handlers; +import static org.junit.Assert.assertTrue; + +/** + * @author gjoranv + * @since 5.1.10 + */ +public class ApplicationStatusHandlerTest { + + @Test + public void application_configs_are_rendered() { + ApplicationMetadataConfig metaConfig = new ApplicationMetadataConfig( + new ApplicationMetadataConfig.Builder() + .checksum("abc") + .name("app") + .path("/a/b/c") + .timestamp(3000) + .user("donald")); + + ApplicationUserdataConfig userConfig = new ApplicationUserdataConfig( + new ApplicationUserdataConfig.Builder() + .version("v1")); + + String json = ApplicationStatusHandler.renderApplicationConfigs(metaConfig, userConfig).toString(); + assertTrue(json.contains("version")); + assertTrue(json.contains("meta")); + assertTrue(json.contains("abc")); + assertTrue(json.contains("app")); + assertTrue(json.contains("/a/b/c")); + assertTrue(json.contains("3000")); + assertTrue(json.contains("donald")); + + assertTrue(json.contains("v1")); + } + + @Test + public void object_components_are_rendered() { + HashMap<ComponentId, Object> id2object = new HashMap<>(); + id2object.put(new ComponentId("myComponent"), new Object()); + + String json = ApplicationStatusHandler.renderObjectComponents(id2object).toString(); + assertTrue(json.contains("myComponent")); + } + + @Test + public void request_handlers_are_rendered() { + final String id = "myHandler"; + final String serverBinding1 = "http://*/serverBinding"; + final String serverBinding2 = "http://*/anotherServerBinding"; + final String clientBinding = "http://*/clientBinding"; + + HashMap<ComponentId, RequestHandler> handlersById = new HashMap<>(); + handlersById.put(new ComponentId(id), Mockito.mock(RequestHandler.class)); + + JdiscBindingsConfig bindingsConfig = new JdiscBindingsConfig(new JdiscBindingsConfig.Builder() + .handlers(id, new Handlers.Builder() + .serverBindings(serverBinding1) + .serverBindings(serverBinding2) + .clientBindings(clientBinding)) + ); + String json = ApplicationStatusHandler.renderRequestHandlers(bindingsConfig, handlersById).toString(); + assertTrue(json.contains("\"" + id + "\"")); + assertTrue(json.contains(serverBinding1)); + assertTrue(json.contains(serverBinding2)); + assertTrue(json.contains(clientBinding)); + } + + @Test + public void client_providers_are_rendered() { + final String id = "myClient"; + final String clientBinding = "http://*/clientBinding"; + final String clientBinding2 = "http://*/anotherClientBinding"; + final String serverBinding = "http://*/serverBinding"; + + HashMap<ComponentId, ClientProvider> clientsById = new HashMap<>(); + clientsById.put(new ComponentId(id), Mockito.mock(ClientProvider.class)); + + JdiscBindingsConfig bindingsConfig = new JdiscBindingsConfig(new JdiscBindingsConfig.Builder() + .handlers(id, new Handlers.Builder() + .clientBindings(clientBinding) + .clientBindings(clientBinding2) + .serverBindings(serverBinding)) + ); + String json = ApplicationStatusHandler.renderRequestHandlers(bindingsConfig, clientsById).toString(); + System.out.println(json); + assertTrue(json.contains("\"" + id + "\"")); + assertTrue(json.contains(clientBinding)); + assertTrue(json.contains(clientBinding2)); + assertTrue(json.contains(serverBinding)); + } + + @Test + public void chains_are_rendered() { + ChainRegistry<Processor> chains = new ChainRegistry<>(); + Chain<Processor> chain = new Chain<>("myChain", new VoidProcessor(new ComponentId("voidProcessor"))); + chains.register(new ComponentId("myChain"), chain); + + String json = ApplicationStatusHandler.StatusResponse.renderChains(chains).toString(); + assertTrue(json.contains("myChain")); + assertTrue(json.contains("voidProcessor")); + } + + private static class VoidProcessor extends Processor { + private VoidProcessor(ComponentId id) { + super(); + initId(id); + } + @Override + public Response process(Request request, Execution processorExecution) { + return null; + } + } +} |