From 9a620aa5703e8b90e7d14d4f581af611576b5e79 Mon Sep 17 00:00:00 2001 From: jmatsuok Date: Tue, 24 Sep 2024 08:45:45 -0400 Subject: [PATCH 1/7] Run spotless --- .../io/cryostat/agent/remote/GCContext.java | 71 +++++++++++++++++++ .../cryostat/agent/remote/RemoteModule.java | 4 ++ 2 files changed, 75 insertions(+) create mode 100644 src/main/java/io/cryostat/agent/remote/GCContext.java diff --git a/src/main/java/io/cryostat/agent/remote/GCContext.java b/src/main/java/io/cryostat/agent/remote/GCContext.java new file mode 100644 index 00000000..eb9f6776 --- /dev/null +++ b/src/main/java/io/cryostat/agent/remote/GCContext.java @@ -0,0 +1,71 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cryostat.agent.remote; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; + +import javax.inject.Inject; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.net.httpserver.HttpExchange; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GCContext implements RemoteContext { + + private final Logger log = LoggerFactory.getLogger(getClass()); + private final ObjectMapper objectMapper; + + @Inject + GCContext(ObjectMapper mapper) { + objectMapper = mapper; + } + + @Override + public String path() { + return "/gc/"; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + try { + String mtd = exchange.getRequestMethod(); + switch (mtd) { + case "GET": + try { + MemoryMXBean memorybean = ManagementFactory.getMemoryMXBean(); + memorybean.gc(); + exchange.sendResponseHeaders(HttpStatus.SC_OK, BODY_LENGTH_UNKNOWN); + } catch (Exception e) { + log.error("failed to fetch memoryMXBean", e); + exchange.sendResponseHeaders( + HttpStatus.SC_INTERNAL_SERVER_ERROR, BODY_LENGTH_NONE); + } + break; + default: + log.warn("Unknown request method {}", mtd); + exchange.sendResponseHeaders( + HttpStatus.SC_METHOD_NOT_ALLOWED, BODY_LENGTH_NONE); + break; + } + } finally { + exchange.close(); + } + } +} diff --git a/src/main/java/io/cryostat/agent/remote/RemoteModule.java b/src/main/java/io/cryostat/agent/remote/RemoteModule.java index 8dd7e82d..f18fd8bf 100644 --- a/src/main/java/io/cryostat/agent/remote/RemoteModule.java +++ b/src/main/java/io/cryostat/agent/remote/RemoteModule.java @@ -22,6 +22,10 @@ @Module public abstract class RemoteModule { + @Binds + @IntoSet + abstract RemoteContext bindGCContext(GCContext ctx); + @Binds @IntoSet abstract RemoteContext bindMBeanContext(MBeanContext ctx); From d82e3715a50b7373fcc6a14c886ee37b3b34cd56 Mon Sep 17 00:00:00 2001 From: jmatsuok Date: Tue, 24 Sep 2024 13:36:34 -0400 Subject: [PATCH 2/7] Rework handler --- .../io/cryostat/agent/remote/GCContext.java | 71 ----------- .../cryostat/agent/remote/InvokeContext.java | 110 ++++++++++++++++++ .../cryostat/agent/remote/RemoteModule.java | 2 +- 3 files changed, 111 insertions(+), 72 deletions(-) delete mode 100644 src/main/java/io/cryostat/agent/remote/GCContext.java create mode 100644 src/main/java/io/cryostat/agent/remote/InvokeContext.java diff --git a/src/main/java/io/cryostat/agent/remote/GCContext.java b/src/main/java/io/cryostat/agent/remote/GCContext.java deleted file mode 100644 index eb9f6776..00000000 --- a/src/main/java/io/cryostat/agent/remote/GCContext.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright The Cryostat Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.cryostat.agent.remote; - -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; - -import javax.inject.Inject; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.sun.net.httpserver.HttpExchange; -import org.apache.http.HttpStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class GCContext implements RemoteContext { - - private final Logger log = LoggerFactory.getLogger(getClass()); - private final ObjectMapper objectMapper; - - @Inject - GCContext(ObjectMapper mapper) { - objectMapper = mapper; - } - - @Override - public String path() { - return "/gc/"; - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - try { - String mtd = exchange.getRequestMethod(); - switch (mtd) { - case "GET": - try { - MemoryMXBean memorybean = ManagementFactory.getMemoryMXBean(); - memorybean.gc(); - exchange.sendResponseHeaders(HttpStatus.SC_OK, BODY_LENGTH_UNKNOWN); - } catch (Exception e) { - log.error("failed to fetch memoryMXBean", e); - exchange.sendResponseHeaders( - HttpStatus.SC_INTERNAL_SERVER_ERROR, BODY_LENGTH_NONE); - } - break; - default: - log.warn("Unknown request method {}", mtd); - exchange.sendResponseHeaders( - HttpStatus.SC_METHOD_NOT_ALLOWED, BODY_LENGTH_NONE); - break; - } - } finally { - exchange.close(); - } - } -} diff --git a/src/main/java/io/cryostat/agent/remote/InvokeContext.java b/src/main/java/io/cryostat/agent/remote/InvokeContext.java new file mode 100644 index 00000000..f2298111 --- /dev/null +++ b/src/main/java/io/cryostat/agent/remote/InvokeContext.java @@ -0,0 +1,110 @@ +/* + * Copyright The Cryostat Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cryostat.agent.remote; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; + +import javax.inject.Inject; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.net.httpserver.HttpExchange; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class InvokeContext implements RemoteContext { + + private final Logger log = LoggerFactory.getLogger(getClass()); + private final ObjectMapper mapper; + + @Inject + InvokeContext(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public String path() { + return "/mbean-invoke/"; + } + + /* + * public T invokeMBeanOperation( + String beanName, + String operation, + Object[] parameters, + String[] signature, + Class returnType) + */ + + @Override + public void handle(HttpExchange exchange) throws IOException { + try { + String mtd = exchange.getRequestMethod(); + switch (mtd) { + case "POST": + try (InputStream body = exchange.getRequestBody()) { + MBeanInvocationRequest req = + mapper.readValue(body, MBeanInvocationRequest.class); + if (req.beanName.equals(ManagementFactory.MEMORY_MXBEAN_NAME)) { + MemoryMXBean bean = ManagementFactory.getMemoryMXBean(); + bean.gc(); + exchange.sendResponseHeaders(HttpStatus.SC_OK, BODY_LENGTH_NONE); + } + } catch (Exception e) { + log.error("mbean serialization failure", e); + exchange.sendResponseHeaders(HttpStatus.SC_BAD_GATEWAY, BODY_LENGTH_NONE); + } + break; + default: + log.warn("Unknown request method {}", mtd); + exchange.sendResponseHeaders( + HttpStatus.SC_METHOD_NOT_ALLOWED, BODY_LENGTH_NONE); + break; + } + } finally { + exchange.close(); + } + } + + static class MBeanInvocationRequest { + + public String beanName; + public String operation; + public Object[] parameters; + public String[] signature; + public Class returnType; + + // Support GC and Thread operations for now + public boolean isValid() { + if (this.beanName.equals(ManagementFactory.MEMORY_MXBEAN_NAME) + || this.beanName.equals(ManagementFactory.THREAD_MXBEAN_NAME)) { + return true; + } + return false; + } + + public String getBeanName() { + return beanName; + } + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + } +} diff --git a/src/main/java/io/cryostat/agent/remote/RemoteModule.java b/src/main/java/io/cryostat/agent/remote/RemoteModule.java index f18fd8bf..c9554eef 100644 --- a/src/main/java/io/cryostat/agent/remote/RemoteModule.java +++ b/src/main/java/io/cryostat/agent/remote/RemoteModule.java @@ -24,7 +24,7 @@ public abstract class RemoteModule { @Binds @IntoSet - abstract RemoteContext bindGCContext(GCContext ctx); + abstract RemoteContext bindInvokeContext(InvokeContext ctx); @Binds @IntoSet From 74381eb488a8d07eb95efa2f8ba8d550c013bdf0 Mon Sep 17 00:00:00 2001 From: jmatsuok Date: Wed, 25 Sep 2024 15:05:50 -0400 Subject: [PATCH 3/7] Generalize handler, remove comment, remove unneeded parameter --- .../cryostat/agent/remote/InvokeContext.java | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/cryostat/agent/remote/InvokeContext.java b/src/main/java/io/cryostat/agent/remote/InvokeContext.java index f2298111..318fcee5 100644 --- a/src/main/java/io/cryostat/agent/remote/InvokeContext.java +++ b/src/main/java/io/cryostat/agent/remote/InvokeContext.java @@ -17,24 +17,29 @@ import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; +import java.util.Objects; import javax.inject.Inject; +import javax.management.MBeanServer; +import javax.management.ObjectName; import com.fasterxml.jackson.databind.ObjectMapper; import com.sun.net.httpserver.HttpExchange; import org.apache.http.HttpStatus; +import org.eclipse.microprofile.config.Config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class InvokeContext implements RemoteContext { +class InvokeContext extends MutatingRemoteContext { private final Logger log = LoggerFactory.getLogger(getClass()); private final ObjectMapper mapper; @Inject - InvokeContext(ObjectMapper mapper) { + InvokeContext(ObjectMapper mapper, Config config) { + super(config); this.mapper = mapper; } @@ -43,15 +48,6 @@ public String path() { return "/mbean-invoke/"; } - /* - * public T invokeMBeanOperation( - String beanName, - String operation, - Object[] parameters, - String[] signature, - Class returnType) - */ - @Override public void handle(HttpExchange exchange) throws IOException { try { @@ -61,10 +57,24 @@ public void handle(HttpExchange exchange) throws IOException { try (InputStream body = exchange.getRequestBody()) { MBeanInvocationRequest req = mapper.readValue(body, MBeanInvocationRequest.class); - if (req.beanName.equals(ManagementFactory.MEMORY_MXBEAN_NAME)) { - MemoryMXBean bean = ManagementFactory.getMemoryMXBean(); - bean.gc(); - exchange.sendResponseHeaders(HttpStatus.SC_OK, BODY_LENGTH_NONE); + if (!req.isValid()) { + exchange.sendResponseHeaders( + HttpStatus.SC_BAD_REQUEST, BODY_LENGTH_NONE); + } + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + Object response = + server.invoke( + ObjectName.getInstance(req.beanName), + req.operation, + req.parameters, + req.signature); + if (Objects.nonNull(response)) { + exchange.sendResponseHeaders(HttpStatus.SC_OK, BODY_LENGTH_UNKNOWN); + try (OutputStream responseStream = exchange.getResponseBody()) { + mapper.writeValue(responseStream, response); + } + } else { + exchange.sendResponseHeaders(HttpStatus.SC_CREATED, BODY_LENGTH_NONE); } } catch (Exception e) { log.error("mbean serialization failure", e); @@ -88,12 +98,10 @@ static class MBeanInvocationRequest { public String operation; public Object[] parameters; public String[] signature; - public Class returnType; // Support GC and Thread operations for now public boolean isValid() { - if (this.beanName.equals(ManagementFactory.MEMORY_MXBEAN_NAME) - || this.beanName.equals(ManagementFactory.THREAD_MXBEAN_NAME)) { + if (this.beanName.equals(ManagementFactory.MEMORY_MXBEAN_NAME)) { return true; } return false; From 6b01cfb28199807ee429f05e70c51ff2778c633e Mon Sep 17 00:00:00 2001 From: jmatsuok Date: Wed, 25 Sep 2024 15:52:10 -0400 Subject: [PATCH 4/7] Change response code --- src/main/java/io/cryostat/agent/remote/InvokeContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/cryostat/agent/remote/InvokeContext.java b/src/main/java/io/cryostat/agent/remote/InvokeContext.java index 318fcee5..b11ed522 100644 --- a/src/main/java/io/cryostat/agent/remote/InvokeContext.java +++ b/src/main/java/io/cryostat/agent/remote/InvokeContext.java @@ -74,7 +74,7 @@ public void handle(HttpExchange exchange) throws IOException { mapper.writeValue(responseStream, response); } } else { - exchange.sendResponseHeaders(HttpStatus.SC_CREATED, BODY_LENGTH_NONE); + exchange.sendResponseHeaders(HttpStatus.SC_ACCEPTED, BODY_LENGTH_NONE); } } catch (Exception e) { log.error("mbean serialization failure", e); From 2d64ea1f25e0c78416de46c7a6cadad9bae27605 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 1 Oct 2024 09:37:56 -0400 Subject: [PATCH 5/7] upgrade Mockito to be buildable with JDK21 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9807b1ac..36b979ea 100644 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ 4.8.6.4 5.11.0 3.0 - 5.2.0 + 5.14.1 0.8.12 2.43.0 1.17.0 @@ -256,7 +256,7 @@ org.mockito - mockito-inline + mockito-core ${org.mockito.version} test From f9dc7c186c8b94d8132de618453d3f5446df5c7e Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 1 Oct 2024 09:38:09 -0400 Subject: [PATCH 6/7] document logger level config --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index ca7a81a0..8606404a 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,18 @@ stopped or the host JVM shuts down. ## Configuration +### Logging + +`cryostat-agent` uses [SLF4J](https://www.slf4j.org/) for its logging. Currently it ships with `slf4j-simple` as the +log implementation, but this should change in the future to hook in to any existing SLF4J providers found on the +classpath at runtime, or fallback to `slf4j-simple` if nothing is found. + +The Agent's log levels can be controlled by setting a JVM system property on the target JVM: +`-Dio.cryostat.agent.shaded.org.slf4j.simpleLogger.defaultLogLevel=trace`, or replace `trace` with whatever level you +prefer. This can also be passed as a dynamic attachment argument when starting the Agent after the target JVM. + +### Agent Properties + `cryostat-agent` uses [smallrye-config](https://github.com/smallrye/smallrye-config) for configuration. Below is a list of configuration properties that can be used to influence how `cryostat-agent` runs and how it advertises itself to a Cryostat server instance. Properties that require configuration are indicated with a checked box. From c9212f624b0218d2e0a9b497636c3d571d2a21fb Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 1 Oct 2024 09:49:49 -0400 Subject: [PATCH 7/7] remove comment Signed-off-by: Andrew Azores --- src/main/java/io/cryostat/agent/remote/InvokeContext.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/io/cryostat/agent/remote/InvokeContext.java b/src/main/java/io/cryostat/agent/remote/InvokeContext.java index b11ed522..6fedc664 100644 --- a/src/main/java/io/cryostat/agent/remote/InvokeContext.java +++ b/src/main/java/io/cryostat/agent/remote/InvokeContext.java @@ -99,7 +99,6 @@ static class MBeanInvocationRequest { public Object[] parameters; public String[] signature; - // Support GC and Thread operations for now public boolean isValid() { if (this.beanName.equals(ManagementFactory.MEMORY_MXBEAN_NAME)) { return true;