diff --git a/src/main/java/io/cryostat/diagnostic/Diagnostics.java b/src/main/java/io/cryostat/diagnostic/Diagnostics.java new file mode 100644 index 000000000..917d9412f --- /dev/null +++ b/src/main/java/io/cryostat/diagnostic/Diagnostics.java @@ -0,0 +1,42 @@ +/* + * 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.diagnostic; + +import io.cryostat.targets.Target; +import io.cryostat.targets.TargetConnectionManager; + +import io.smallrye.common.annotation.Blocking; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import org.jboss.resteasy.reactive.RestPath; + +@Path("/api/beta/diagnostics/targets/{targetId}") +public class Diagnostics { + + @Inject TargetConnectionManager targetConnectionManager; + + @Path("/gc") + @RolesAllowed("write") + @Blocking + @POST + public void gc(@RestPath long targetId) { + targetConnectionManager.executeConnectedTask( + Target.getTargetById(targetId), + conn -> conn.invokeMBeanOperation("java.lang:type=Memory", "gc", null, null, Void.class)); + } +} diff --git a/src/main/java/io/cryostat/targets/AgentClient.java b/src/main/java/io/cryostat/targets/AgentClient.java index 146d96ab9..b9e53ba05 100644 --- a/src/main/java/io/cryostat/targets/AgentClient.java +++ b/src/main/java/io/cryostat/targets/AgentClient.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.openjdk.jmc.common.unit.IConstrainedMap; @@ -108,6 +109,53 @@ Uni mbeanMetrics() { .map(Unchecked.function(s -> mapper.readValue(s, MBeanMetrics.class))); } + Uni invokeMBeanOperation( + String beanName, + String operation, + Object[] parameters, + String[] signature, + Class returnType) { + try { + var req = new MBeanInvocationRequest(beanName, operation, parameters, signature); + return invoke( + HttpMethod.POST, + "/mbean-invoke/", + Buffer.buffer(mapper.writeValueAsBytes(req)), + BodyCodec.buffer()) + .map( + Unchecked.function( + resp -> { + int statusCode = resp.statusCode(); + if (HttpStatusCodeIdentifier.isSuccessCode(statusCode)) { + return resp; + } else if (statusCode == 403) { + logger.errorv( + "invokeMBeanOperation {0} ({1}) for {2} failed:" + + " HTTP 403", + beanName, operation, getUri()); + throw new ForbiddenException( + new UnsupportedOperationException( + "startRecording")); + } else { + logger.errorv( + "invokeMBeanOperation for {0} for ({1}) {2}" + + " failed: HTTP {3}", + beanName, operation, getUri(), statusCode); + throw new AgentApiException(statusCode); + } + })) + .map(HttpResponse::bodyAsBuffer) + .map( + buff -> { + // TODO implement conditional handling based on expected returnType + return null; + }); + } catch (JsonProcessingException e) { + logger.error("invokeMBeanOperation request failed", e); + return Uni.createFrom().failure(e); + } + } + Uni startRecording(StartRecordingRequest req) { try { return invoke( @@ -568,4 +616,12 @@ public IOptionDescriptor getOptionInfo(String s) { return getOptionDescriptors().get(s); } } + + static record MBeanInvocationRequest( + String beanName, String operation, Object[] parameters, String[] signature) { + MBeanInvocationRequest { + Objects.requireNonNull(beanName); + Objects.requireNonNull(operation); + } + } } diff --git a/src/main/java/io/cryostat/targets/AgentConnection.java b/src/main/java/io/cryostat/targets/AgentConnection.java index 658b93a5f..c1dede725 100644 --- a/src/main/java/io/cryostat/targets/AgentConnection.java +++ b/src/main/java/io/cryostat/targets/AgentConnection.java @@ -108,6 +108,18 @@ public JvmIdentifier getJvmIdentifier() throws IDException, IOException { } } + @Override + public T invokeMBeanOperation( + String beanName, + String operation, + Object[] parameters, + String[] signature, + Class returnType) { + return client.invokeMBeanOperation(beanName, operation, parameters, signature, returnType) + .await() + .atMost(client.getTimeout()); + } + @Override public int getPort() { return getUri().getPort();