From ff6c4cd55ec35341dd5ffda6ba6be14d5f0c4b82 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 30 Jan 2023 13:23:50 -0500 Subject: [PATCH] fix(signals): handle shutdown signals cleanly on failed startup (#44) * fix(signals): handle shutdown signals cleanly on failed startup * add config property to replace magic number * guard against null http which can occur in startup failure * add Runtime shutdown hook to help ensure clean shutdown * fix null value in log message * fix formatting of comment * move two config properties under 'webclient' subsection * extract a magic constant to config * fixup! move two config properties under 'webclient' subsection * extract fixed constant to config property * extract max upload timeout to config property * DI object mapper * correct reported default value * extract list of handled signals to config property * set safe ssl defaults * update README * use extract variable ref --- README.md | 11 +- src/main/java/io/cryostat/agent/Agent.java | 227 +++++++++++++----- .../java/io/cryostat/agent/ConfigModule.java | 66 ++++- .../io/cryostat/agent/CryostatClient.java | 21 +- .../java/io/cryostat/agent/MainModule.java | 34 ++- .../java/io/cryostat/agent/Registration.java | 4 +- .../java/io/cryostat/agent/WebServer.java | 6 +- .../META-INF/microprofile-config.properties | 19 +- 8 files changed, 295 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 45512d32..dc82e6f9 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,21 @@ and how it advertises itself to a Cryostat server instance. Required properties - [ ] `cryostat.agent.hostname` [`String`]: the hostname for this application instance. This will be used for the published JMX connection URL. If not provided then the default is to attempt to resolve the localhost hostname. - [ ] `cryostat.agent.realm` [`String`]: the Cryostat Discovery API "realm" that this agent belongs to. This should be unique per agent instance. The default includes the `cryostat.agent.app.name` and a random UUID. - [ ] `cryostat.agent.authorization` [`String`]: Authorization header value to include with API requests to the Cryostat server, ex. `Bearer abcd1234`. Default `None`. -- [ ] `cryostat.agent.ssl.trust-all` [`boolean`]: Control whether the agent trusts all certificates presented by the Cryostat server. Default `true`. -- [ ] `cryostat.agent.ssl.verify-hostname` [`boolean`]: Control whether the agent verifies hostnames on certificates presented by the Cryostat server. Default `false`. +- [ ] `cryostat.agent.webclient.ssl.trust-all` [`boolean`]: Control whether the agent trusts all certificates presented by the Cryostat server. Default `false`. This should only be overridden for development and testing purposes, never in production. +- [ ] `cryostat.agent.webclient.ssl.verify-hostname` [`boolean`]: Control whether the agent verifies hostnames on certificates presented by the Cryostat server. Default `true`. This should only be overridden for development and testing purposes, never in production. +- [ ] `cryostat.agent.webclient.connect.timeout-ms` [`long`]: the duration in milliseconds to wait for HTTP requests to the Cryostat server to connect. Default `1000`. +- [ ] `cryostat.agent.webclient.response.timeout-ms` [`long`]: the duration in milliseconds to wait for HTTP requests to the Cryostat server to respond. Default `1000`. - [ ] `cryostat.agent.webserver.host` [`String`]: the internal hostname or IP address for the embedded webserver to bind to. Default `0.0.0.0`. - [ ] `cryostat.agent.webserver.port` [`int`]: the internal port number for the embedded webserver to bind to. Default `9977`. - [ ] `cryostat.agent.app.name` [`String`]: a human-friendly name for this application. Default `cryostat-agent`. - [ ] `cryostat.agent.app.jmx.port` [`int`]: the JMX RMI port that the application is listening on. The default is to attempt to determine this from the `com.sun.management.jmxremote.port` system property. - [ ] `cryostat.agent.registration.retry-ms` [`long`]: the duration in milliseconds between attempts to register with the Cryostat server. Default `5000`. +- [ ] `cryostat.agent.exit.signals` [`[String]`]: a comma-separated list of signals that the agent should handle. When any of these signals is caught the agent initiates an orderly shutdown, deregistering from the Cryostat server and potentially uploading the latest harvested JFR data. Default `INT,TERM`. +- [ ] `cryostat.agent.exit.deregistration.timeout-ms` [`long`]: the duration in milliseconds to wait for a response from the Cryostat server when attempting to deregister at shutdown time . Default `3s`. - [ ] `cryostat.agent.harvester.period-ms` [`long`]: the length of time between JFR collections and pushes by the harvester. This also controls the maximum age of data stored in the buffer for the harvester's managed Flight Recording. Every `period-ms` the harvester will upload a JFR binary file to the `cryostat.agent.baseuri` archives. Default `-1`, which indicates no harvesting will be performed. - [ ] `cryostat.agent.harvester.template` [`String`]: the name of the `.jfc` event template configuration to use for the harvester's managed Flight Recording. Default `default`, the continuous monitoring event template. -- [ ] `cryostat.agent.harvester.max-files` [`String`]: the maximum number of pushed files that Cryostat will keep over the network from the agent. This is supplied to the harvester's push requests which instructs Cryostat to prune, in a FIFO manner, the oldest JFR files within the attached JVM target's storage, while the number of stored recordings is greater than this configuration's maximum file limit. Default `10`. +- [ ] `cryostat.agent.harvester.max-files` [`String`]: the maximum number of pushed files that Cryostat will keep over the network from the agent. This is supplied to the harvester's push requests which instructs Cryostat to prune, in a FIFO manner, the oldest JFR files within the attached JVM target's storage, while the number of stored recordings is greater than this configuration's maximum file limit. Default `2147483647` (`Integer.MAX_VALUE`). +- [ ] `cryostat.agent.harvester.upload.timeout-ms` [`long`]: the duration in milliseconds to wait for HTTP upload requests to the Cryostat server to complete and respond. Default `30000`. - [ ] `cryostat.agent.harvester.exit.max-age-ms` [`long`]: the JFR `maxage` setting, specified in milliseconds, to apply to recording data uploaded to the Cryostat server when the JVM this Agent instance is attached to exits. This ensures that tail-end data is captured between the last periodic push and the application exit. Exit uploads only occur when the application receives `SIGINT`/`SIGTERM` from the operating system or container platform. - [ ] `cryostat.agent.harvester.exit.max-size-b` [`long`]: the JFR `maxsize` setting, specified in bytes, to apply to exit uploads as described above. diff --git a/src/main/java/io/cryostat/agent/Agent.java b/src/main/java/io/cryostat/agent/Agent.java index 5c90b927..680a9c62 100644 --- a/src/main/java/io/cryostat/agent/Agent.java +++ b/src/main/java/io/cryostat/agent/Agent.java @@ -37,11 +37,17 @@ */ package io.cryostat.agent; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.inject.Named; import javax.inject.Singleton; import dagger.Component; @@ -53,73 +59,86 @@ public class Agent { private static Logger log = LoggerFactory.getLogger(Agent.class); + private static final AtomicBoolean needsCleanup = new AtomicBoolean(true); public static void main(String[] args) { - final Client client = DaggerAgent_Client.builder().build(); - - List.of(new Signal("INT"), new Signal("TERM")) - .forEach( - signal -> { - SignalHandler oldHandler = Signal.handle(signal, s -> {}); - SignalHandler handler = - s -> { - log.info("Caught SIG{}({})", s.getName(), s.getNumber()); - try { - client.harvester().exitUpload().get(); - } catch (InterruptedException | ExecutionException e) { - log.error("Exit upload failed", e); - } finally { - client.registration() - .deregister() - .orTimeout(1, TimeUnit.SECONDS) - .thenRunAsync( - () -> { - try { - log.info("Shutting down..."); - client.webServer().stop(); - client.registration().stop(); - client.executor().shutdown(); - } catch (Exception e) { - log.warn( - "Exception during" - + " shutdown", - e); - } finally { - log.info("Shutdown complete"); - oldHandler.handle(s); - } - }, - client.executor()); - } - }; - Signal.handle(signal, handler); - }); - + AgentExitHandler agentExitHandler = null; try { - client.registration() - .addRegistrationListener( - evt -> { - switch (evt.state) { - case REGISTERED: - client.harvester().start(); - break; - case UNREGISTERED: - client.harvester().stop(); - break; - case REFRESHED: - break; - default: - log.error("Unknown registration state: {}", evt.state); - break; + final Client client = DaggerAgent_Client.builder().build(); + Registration registration = client.registration(); + Harvester harvester = client.harvester(); + WebServer webServer = client.webServer(); + ExecutorService executor = client.executor(); + List exitSignals = client.exitSignals(); + long exitDeregistrationTimeout = client.exitDeregistrationTimeout(); + + agentExitHandler = + installSignalHandlers( + exitSignals, + registration, + harvester, + webServer, + executor, + exitDeregistrationTimeout); + final AgentExitHandler fHandler = agentExitHandler; + Thread t = + new Thread( + () -> { + if (needsCleanup.getAndSet(false)) { + fHandler.performCleanup(null); } }); - client.registration().start(); - client.webServer().start(); + t.setName("cryostat-agent-shutdown"); + t.setDaemon(false); + Runtime.getRuntime().addShutdownHook(t); + + registration.addRegistrationListener( + evt -> { + switch (evt.state) { + case REGISTERED: + harvester.start(); + break; + case UNREGISTERED: + harvester.stop(); + break; + case REFRESHED: + break; + default: + log.error("Unknown registration state: {}", evt.state); + break; + } + }); + registration.start(); + webServer.start(); + log.info("Startup complete"); } catch (Exception e) { log.error(Agent.class.getSimpleName() + " startup failure", e); - return; + if (agentExitHandler != null) { + agentExitHandler.reset(); + } + } + } + + private static AgentExitHandler installSignalHandlers( + List exitSignals, + Registration registration, + Harvester harvester, + WebServer webServer, + ExecutorService executor, + long exitDeregistrationTimeout) { + AgentExitHandler agentExitHandler = + new AgentExitHandler( + registration, harvester, webServer, executor, exitDeregistrationTimeout); + for (String s : exitSignals) { + Signal signal = new Signal(s); + try { + SignalHandler oldHandler = Signal.handle(signal, agentExitHandler); + agentExitHandler.setOldHandler(signal, oldHandler); + } catch (IllegalArgumentException iae) { + log.warn("Unable to register signal handler for SIG" + s, iae); + } } - log.info("Startup complete"); + return agentExitHandler; } public static void agentmain(String args) { @@ -130,7 +149,7 @@ public static void agentmain(String args) { main(args == null ? new String[0] : args.split("\\s")); }); t.setDaemon(true); - t.setName("cryostat-agent"); + t.setName("cryostat-agent-main"); t.start(); } @@ -149,9 +168,97 @@ interface Client { ScheduledExecutorService executor(); + @Named(ConfigModule.CRYOSTAT_AGENT_EXIT_SIGNALS) + List exitSignals(); + + @Named(ConfigModule.CRYOSTAT_AGENT_EXIT_DEREGISTRATION_TIMEOUT_MS) + long exitDeregistrationTimeout(); + @Component.Builder interface Builder { Client build(); } } + + private static class AgentExitHandler implements SignalHandler { + + private static Logger log = LoggerFactory.getLogger(Agent.class); + + private final Map oldHandlers = new HashMap<>(); + private final Registration registration; + private final Harvester harvester; + private final WebServer webServer; + private final ExecutorService executor; + private final long exitDeregistrationTimeout; + + private AgentExitHandler( + Registration registration, + Harvester harvester, + WebServer webServer, + ExecutorService executor, + long exitDeregistrationTimeout) { + this.registration = Objects.requireNonNull(registration); + this.harvester = Objects.requireNonNull(harvester); + this.webServer = Objects.requireNonNull(webServer); + this.executor = Objects.requireNonNull(executor); + this.exitDeregistrationTimeout = exitDeregistrationTimeout; + } + + void setOldHandler(Signal signal, SignalHandler oldHandler) { + this.oldHandlers.put(signal, oldHandler); + } + + @Override + public void handle(Signal sig) { + log.info("Caught SIG{}({})", sig.getName(), sig.getNumber()); + if (needsCleanup.getAndSet(false)) { + performCleanup(sig); + } + } + + void performCleanup(Signal sig) { + log.info("Performing cleanup..."); + try { + harvester.exitUpload().get(); + } catch (InterruptedException | ExecutionException e) { + log.error("Exit upload failed", e); + } finally { + registration + .deregister() + .orTimeout(exitDeregistrationTimeout, TimeUnit.MILLISECONDS) + .handleAsync( + (v, t) -> { + try { + log.info("Shutting down..."); + safeCall(webServer::stop); + safeCall(registration::stop); + safeCall(executor::shutdown); + } finally { + log.info("Shutdown complete"); + if (sig != null) { + // pass signal on to whatever would have handled it had + // this Agent not been installed, so host application + // has a chance to perform a graceful shutdown + oldHandlers.get(sig).handle(sig); + } + } + return null; + }, + executor); + } + } + + void reset() { + this.oldHandlers.forEach(Signal::handle); + this.oldHandlers.clear(); + } + + private void safeCall(Runnable r) { + try { + r.run(); + } catch (Exception e) { + log.warn("Exception during shutdown", e); + } + } + } } diff --git a/src/main/java/io/cryostat/agent/ConfigModule.java b/src/main/java/io/cryostat/agent/ConfigModule.java index 055911f8..de429da3 100644 --- a/src/main/java/io/cryostat/agent/ConfigModule.java +++ b/src/main/java/io/cryostat/agent/ConfigModule.java @@ -40,6 +40,8 @@ import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; import javax.inject.Named; import javax.inject.Singleton; @@ -60,9 +62,15 @@ public abstract class ConfigModule { public static final String CRYOSTAT_AGENT_CALLBACK = "cryostat.agent.callback"; public static final String CRYOSTAT_AGENT_REALM = "cryostat.agent.realm"; public static final String CRYOSTAT_AGENT_AUTHORIZATION = "cryostat.agent.authorization"; - public static final String CRYOSTAT_AGENT_SSL_TRUST_ALL = "cryostat.agent.ssl.trust-all"; - public static final String CRYOSTAT_AGENT_SSL_VERIFY_HOSTNAME = - "cryostat.agent.ssl.verify-hostname"; + + public static final String CRYOSTAT_AGENT_WEBCLIENT_SSL_TRUST_ALL = + "cryostat.agent.webclient.ssl.trust-all"; + public static final String CRYOSTAT_AGENT_WEBCLIENT_SSL_VERIFY_HOSTNAME = + "cryostat.agent.webclient.ssl.verify-hostname"; + public static final String CRYOSTAT_AGENT_WEBCLIENT_CONNECT_TIMEOUT_MS = + "cryostat.agent.webclient.connect.timeout-ms"; + public static final String CRYOSTAT_AGENT_WEBCLIENT_RESPONSE_TIMEOUT_MS = + "cryostat.agent.webclient.response.timeout-ms"; public static final String CRYOSTAT_AGENT_WEBSERVER_HOST = "cryostat.agent.webserver.host"; public static final String CRYOSTAT_AGENT_WEBSERVER_PORT = "cryostat.agent.webserver.port"; @@ -72,6 +80,9 @@ public abstract class ConfigModule { public static final String CRYOSTAT_AGENT_APP_JMX_PORT = "cryostat.agent.app.jmx.port"; public static final String CRYOSTAT_AGENT_REGISTRATION_RETRY_MS = "cryostat.agent.registration.retry-ms"; + public static final String CRYOSTAT_AGENT_EXIT_SIGNALS = "cryostat.agent.exit.signals"; + public static final String CRYOSTAT_AGENT_EXIT_DEREGISTRATION_TIMEOUT_MS = + "cryostat.agent.exit.deregistration.timeout-ms"; public static final String CRYOSTAT_AGENT_HARVESTER_PERIOD_MS = "cryostat.agent.harvester.period-ms"; @@ -79,6 +90,8 @@ public abstract class ConfigModule { "cryostat.agent.harvester.template"; public static final String CRYOSTAT_AGENT_HARVESTER_MAX_FILES = "cryostat.agent.harvester.max-files"; + public static final String CRYOSTAT_AGENT_HARVESTER_UPLOAD_TIMEOUT_MS = + "cryostat.agent.harvester.upload.timeout-ms"; public static final String CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_AGE_MS = "cryostat.agent.harvester.exit.max-age-ms"; public static final String CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_SIZE_B = @@ -121,16 +134,30 @@ public static String provideCryostatAgentAuthorization(SmallRyeConfig config) { @Provides @Singleton - @Named(CRYOSTAT_AGENT_SSL_TRUST_ALL) - public static boolean provideCryostatAgentTrustAll(SmallRyeConfig config) { - return config.getValue(CRYOSTAT_AGENT_SSL_TRUST_ALL, boolean.class); + @Named(CRYOSTAT_AGENT_WEBCLIENT_SSL_TRUST_ALL) + public static boolean provideCryostatAgentWebclientTrustAll(SmallRyeConfig config) { + return config.getValue(CRYOSTAT_AGENT_WEBCLIENT_SSL_TRUST_ALL, boolean.class); + } + + @Provides + @Singleton + @Named(CRYOSTAT_AGENT_WEBCLIENT_SSL_VERIFY_HOSTNAME) + public static boolean provideCryostatAgentWebclientVerifyHostname(SmallRyeConfig config) { + return config.getValue(CRYOSTAT_AGENT_WEBCLIENT_SSL_VERIFY_HOSTNAME, boolean.class); } @Provides @Singleton - @Named(CRYOSTAT_AGENT_SSL_VERIFY_HOSTNAME) - public static boolean provideCryostatAgentVerifyHostname(SmallRyeConfig config) { - return config.getValue(CRYOSTAT_AGENT_SSL_VERIFY_HOSTNAME, boolean.class); + @Named(CRYOSTAT_AGENT_WEBCLIENT_CONNECT_TIMEOUT_MS) + public static long provideCryostatAgentWebclientConnectTimeoutMs(SmallRyeConfig config) { + return config.getValue(CRYOSTAT_AGENT_WEBCLIENT_CONNECT_TIMEOUT_MS, long.class); + } + + @Provides + @Singleton + @Named(CRYOSTAT_AGENT_WEBCLIENT_RESPONSE_TIMEOUT_MS) + public static long provideCryostatAgentWebclientResponseTimeoutMs(SmallRyeConfig config) { + return config.getValue(CRYOSTAT_AGENT_WEBCLIENT_RESPONSE_TIMEOUT_MS, long.class); } @Provides @@ -208,6 +235,13 @@ public static int provideCryostatAgentHarvesterMaxFiles(SmallRyeConfig config) { return config.getValue(CRYOSTAT_AGENT_HARVESTER_MAX_FILES, int.class); } + @Provides + @Singleton + @Named(CRYOSTAT_AGENT_HARVESTER_UPLOAD_TIMEOUT_MS) + public static long provideCryostatAgentHarvesterUploadTimeoutMs(SmallRyeConfig config) { + return config.getValue(CRYOSTAT_AGENT_HARVESTER_UPLOAD_TIMEOUT_MS, long.class); + } + @Provides @Singleton @Named(CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_AGE_MS) @@ -221,4 +255,18 @@ public static long provideCryostatAgentHarvesterMaxAge(SmallRyeConfig config) { public static long provideCryostatAgentHarvesterMaxSize(SmallRyeConfig config) { return config.getValue(CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_SIZE_B, long.class); } + + @Provides + @Singleton + @Named(CRYOSTAT_AGENT_EXIT_SIGNALS) + public static List provideCryostatAgentExitSignals(SmallRyeConfig config) { + return Arrays.asList(config.getValue(CRYOSTAT_AGENT_EXIT_SIGNALS, String.class).split(",")); + } + + @Provides + @Singleton + @Named(CRYOSTAT_AGENT_EXIT_DEREGISTRATION_TIMEOUT_MS) + public static long provideCryostatAgentExitDeregistrationTimeoutMs(SmallRyeConfig config) { + return config.getValue(CRYOSTAT_AGENT_EXIT_DEREGISTRATION_TIMEOUT_MS, long.class); + } } diff --git a/src/main/java/io/cryostat/agent/CryostatClient.java b/src/main/java/io/cryostat/agent/CryostatClient.java index 2362926e..cc07e854 100644 --- a/src/main/java/io/cryostat/agent/CryostatClient.java +++ b/src/main/java/io/cryostat/agent/CryostatClient.java @@ -80,8 +80,8 @@ public class CryostatClient { private final Logger log = LoggerFactory.getLogger(getClass()); - private final ObjectMapper mapper; private final HttpClient http; + private final ObjectMapper mapper; private final String appName; private final String jvmId; @@ -89,23 +89,30 @@ public class CryostatClient { private final URI callback; private final String realm; private final String authorization; + private final long responseTimeoutMs; + private final long uploadTimeoutMs; CryostatClient( HttpClient http, + ObjectMapper mapper, String jvmId, String appName, URI baseUri, URI callback, String realm, - String authorization) { + String authorization, + long responseTimeoutMs, + long uploadTimeoutMs) { this.http = http; + this.mapper = mapper; this.jvmId = jvmId; this.appName = appName; this.baseUri = baseUri; this.callback = callback; this.realm = realm; this.authorization = authorization; - this.mapper = new ObjectMapper(); + this.responseTimeoutMs = responseTimeoutMs; + this.uploadTimeoutMs = uploadTimeoutMs; log.info("Using Cryostat baseuri {}", baseUri); } @@ -121,7 +128,7 @@ public CompletableFuture register(PluginInfo pluginInfo) { HttpRequest.BodyPublishers.ofString( mapper.writeValueAsString(registrationInfo))) .setHeader("Authorization", authorization) - .timeout(Duration.ofSeconds(1)) + .timeout(Duration.ofMillis(responseTimeoutMs)) .build(); log.trace("{}", req); } catch (JsonProcessingException e) { @@ -170,7 +177,7 @@ public CompletableFuture deregister(PluginInfo pluginInfo) { + "?token=" + pluginInfo.getToken())) .DELETE() - .timeout(Duration.ofSeconds(1)) + .timeout(Duration.ofMillis(responseTimeoutMs)) .build(); log.trace("{}", req); return http.sendAsync(req, BodyHandlers.discarding()) @@ -202,7 +209,7 @@ public CompletableFuture update(PluginInfo pluginInfo, Set HttpRequest.BodyPublishers.ofString( mapper.writeValueAsString(subtree))) .setHeader("Authorization", authorization) - .timeout(Duration.ofSeconds(1)) + .timeout(Duration.ofMillis(responseTimeoutMs)) .build(); log.trace("{}", req); return http.sendAsync(req, BodyHandlers.discarding()) @@ -247,7 +254,7 @@ public CompletableFuture upload( .setHeader( "Content-Type", String.format("multipart/form-data; boundary=%s", boundary)) - .timeout(Duration.ofSeconds(30)) + .timeout(Duration.ofMillis(uploadTimeoutMs)) .build(); log.trace("{}", req); return http.sendAsync(req, BodyHandlers.discarding()) diff --git a/src/main/java/io/cryostat/agent/MainModule.java b/src/main/java/io/cryostat/agent/MainModule.java index faba89c4..50848742 100644 --- a/src/main/java/io/cryostat/agent/MainModule.java +++ b/src/main/java/io/cryostat/agent/MainModule.java @@ -62,6 +62,7 @@ import io.cryostat.core.sys.FileSystem; import io.cryostat.core.tui.ClientWriter; +import com.fasterxml.jackson.databind.ObjectMapper; import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -110,7 +111,7 @@ public static WebServer provideWebServer( @Provides @Singleton public static SSLContext provideSslContext( - @Named(ConfigModule.CRYOSTAT_AGENT_SSL_TRUST_ALL) boolean trustAll) { + @Named(ConfigModule.CRYOSTAT_AGENT_WEBCLIENT_SSL_TRUST_ALL) boolean trustAll) { try { if (!trustAll) { return SSLContext.getDefault(); @@ -147,29 +148,52 @@ public X509Certificate[] getAcceptedIssuers() { public static HttpClient provideHttpClient( ScheduledExecutorService executor, SSLContext sslContext, - @Named(ConfigModule.CRYOSTAT_AGENT_SSL_VERIFY_HOSTNAME) boolean verifyHostname) { + @Named(ConfigModule.CRYOSTAT_AGENT_WEBCLIENT_SSL_VERIFY_HOSTNAME) + boolean verifyHostname, + @Named(ConfigModule.CRYOSTAT_AGENT_WEBCLIENT_CONNECT_TIMEOUT_MS) + long connectTimeoutMs) { System.getProperties() .setProperty( "jdk.internal.httpclient.disableHostnameVerification", Boolean.toString(!verifyHostname)); return HttpClient.newBuilder() .executor(executor) - .connectTimeout(Duration.ofSeconds(1)) + .connectTimeout(Duration.ofMillis(connectTimeoutMs)) .sslContext(sslContext) .build(); } + @Provides + @Singleton + public static ObjectMapper provideObjectMapper() { + return new ObjectMapper(); + } + @Provides @Singleton public static CryostatClient provideCryostatClient( HttpClient http, + ObjectMapper objectMapper, @Named(JVM_ID) String jvmId, @Named(ConfigModule.CRYOSTAT_AGENT_APP_NAME) String appName, @Named(ConfigModule.CRYOSTAT_AGENT_BASEURI) URI baseUri, @Named(ConfigModule.CRYOSTAT_AGENT_CALLBACK) URI callback, @Named(ConfigModule.CRYOSTAT_AGENT_REALM) String realm, - @Named(ConfigModule.CRYOSTAT_AGENT_AUTHORIZATION) String authorization) { - return new CryostatClient(http, jvmId, appName, baseUri, callback, realm, authorization); + @Named(ConfigModule.CRYOSTAT_AGENT_AUTHORIZATION) String authorization, + @Named(ConfigModule.CRYOSTAT_AGENT_HARVESTER_UPLOAD_TIMEOUT_MS) long responseTimeoutMs, + @Named(ConfigModule.CRYOSTAT_AGENT_WEBCLIENT_RESPONSE_TIMEOUT_MS) + long uploadTimeoutMs) { + return new CryostatClient( + http, + objectMapper, + jvmId, + appName, + baseUri, + callback, + realm, + authorization, + responseTimeoutMs, + uploadTimeoutMs); } @Provides diff --git a/src/main/java/io/cryostat/agent/Registration.java b/src/main/java/io/cryostat/agent/Registration.java index 703caffa..6e35da1b 100644 --- a/src/main/java/io/cryostat/agent/Registration.java +++ b/src/main/java/io/cryostat/agent/Registration.java @@ -221,8 +221,6 @@ CompletableFuture deregister() { return cryostat.deregister(pluginInfo) .handleAsync( (n, t) -> { - this.pluginInfo.clear(); - notify(RegistrationEvent.State.UNREGISTERED); if (t != null) { log.warn( "Failed to deregister as Cryostat discovery plugin [{}]", @@ -232,6 +230,8 @@ CompletableFuture deregister() { "Deregistered from Cryostat discovery plugin [{}]", this.pluginInfo.getId()); } + this.pluginInfo.clear(); + notify(RegistrationEvent.State.UNREGISTERED); return null; }, executor); diff --git a/src/main/java/io/cryostat/agent/WebServer.java b/src/main/java/io/cryostat/agent/WebServer.java index c8398212..4b28f88a 100644 --- a/src/main/java/io/cryostat/agent/WebServer.java +++ b/src/main/java/io/cryostat/agent/WebServer.java @@ -97,7 +97,9 @@ public void handle(HttpExchange exchange) throws IOException { } void stop() { - this.http.stop(0); - this.http = null; + if (this.http != null) { + this.http.stop(0); + this.http = null; + } } } diff --git a/src/main/resources/META-INF/microprofile-config.properties b/src/main/resources/META-INF/microprofile-config.properties index 219ad1e9..8d6af095 100644 --- a/src/main/resources/META-INF/microprofile-config.properties +++ b/src/main/resources/META-INF/microprofile-config.properties @@ -1,15 +1,24 @@ +cryostat.agent.app.name=cryostat-agent cryostat.agent.baseuri= -cryostat.agent.ssl.trust-all=true -cryostat.agent.ssl.verify-hostname=false + +cryostat.agent.webclient.ssl.trust-all=false +cryostat.agent.webclient.ssl.verify-hostname=true +cryostat.agent.webclient.connect.timeout-ms=1000 +cryostat.agent.webclient.response.timeout-ms=1000 +cryostat.agent.webserver.host=0.0.0.0 +cryostat.agent.webserver.port=9977 + cryostat.agent.authorization=None cryostat.agent.callback= cryostat.agent.realm= + +cryostat.agent.exit.signals=INT,TERM cryostat.agent.registration.retry-ms=5000 -cryostat.agent.webserver.host=0.0.0.0 -cryostat.agent.webserver.port=9977 -cryostat.agent.app.name=cryostat-agent +cryostat.agent.exit.deregistration.timeout-ms=3000 + cryostat.agent.harvester.period-ms=-1 cryostat.agent.harvester.template=default cryostat.agent.harvester.max-files=2147483647 +cryostat.agent.harvester.upload.timeout-ms=30000 cryostat.agent.harvester.exit.max-age-ms=0 cryostat.agent.harvester.exit.max-size-b=0