diff --git a/src/main/java/io/cryostat/mbean/MbeanQuery.java b/src/main/java/io/cryostat/mbean/MbeanQuery.java new file mode 100644 index 000000000..a1c980ec6 --- /dev/null +++ b/src/main/java/io/cryostat/mbean/MbeanQuery.java @@ -0,0 +1,67 @@ +/* + * 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.mbean; + +import java.time.Duration; +import java.util.List; + +import io.cryostat.ConfigProperties; +import io.cryostat.libcryostat.net.MbeanAttributeMap; +import io.cryostat.targets.Target; +import io.cryostat.targets.TargetConnectionManager; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.security.RolesAllowed; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.openapi.annotations.Operation; +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.RestPath; + +@Path("/") +public class MbeanQuery { + + @Inject Logger log; + + @Inject TargetConnectionManager targetConnectionManager; + + @ConfigProperty(name = ConfigProperties.CONNECTIONS_UPLOAD_TIMEOUT) + Duration uploadFailedTimeout; + + @Inject ObjectMapper mapper; + + @Path("api/beta/targets/{targetId}/mbean-query") + @RolesAllowed("read") + @Transactional + @Produces({MediaType.APPLICATION_JSON}) + @GET + @Operation(summary = "Retrieve all currently available mbean attributes") + public List queryMbeans(@RestPath long targetId) { + log.tracev("Mbean query received for target {0}", targetId); + Target target = Target.getTargetById(targetId); + return targetConnectionManager.executeConnectedTask( + target, + conn -> { + return conn.queryMbeanAttributes(); + }, + uploadFailedTimeout); + } +} diff --git a/src/main/java/io/cryostat/targets/AgentClient.java b/src/main/java/io/cryostat/targets/AgentClient.java index b1ac86622..78d5992d3 100644 --- a/src/main/java/io/cryostat/targets/AgentClient.java +++ b/src/main/java/io/cryostat/targets/AgentClient.java @@ -48,6 +48,7 @@ import io.cryostat.discovery.DiscoveryPlugin; import io.cryostat.discovery.DiscoveryPlugin.PluginCallback.DiscoveryPluginAuthorizationHeaderFactory; import io.cryostat.libcryostat.net.MBeanMetrics; +import io.cryostat.libcryostat.net.MbeanAttributeMap; import io.cryostat.libcryostat.triggers.SmartTrigger; import io.cryostat.targets.AgentJFRService.StartRecordingRequest; import io.cryostat.util.HttpStatusCodeIdentifier; @@ -249,6 +250,20 @@ Uni invokeMBeanOperation( } } + Uni> queryMbeanAttributes() { + return agentRestClient + .queryMbeanAttributes() + .map( + Unchecked.function( + resp -> { + try (resp; + var is = (InputStream) resp.getEntity()) { + return Arrays.asList( + mapper.readValue(is, MbeanAttributeMap[].class)); + } + })); + } + Uni startRecording(StartRecordingRequest req) { return agentRestClient .startRecording(req) diff --git a/src/main/java/io/cryostat/targets/AgentConnection.java b/src/main/java/io/cryostat/targets/AgentConnection.java index 79f3896a5..f36e4366a 100644 --- a/src/main/java/io/cryostat/targets/AgentConnection.java +++ b/src/main/java/io/cryostat/targets/AgentConnection.java @@ -42,6 +42,7 @@ import io.cryostat.libcryostat.net.CryostatAgentMXBean; import io.cryostat.libcryostat.net.IDException; import io.cryostat.libcryostat.net.MBeanMetrics; +import io.cryostat.libcryostat.net.MbeanAttributeMap; import io.cryostat.libcryostat.sys.Clock; import io.cryostat.libcryostat.triggers.SmartTrigger; import io.cryostat.targets.AgentClient.AsyncProfile; @@ -161,6 +162,15 @@ public T invokeMBeanOperation( .atMost(client.getTimeout()); } + @Override + public List queryMbeanAttributes() + throws IOException, + InstanceNotFoundException, + IntrospectionException, + ReflectionException { + return client.queryMbeanAttributes().await().atMost(client.getTimeout()); + } + @Override public List listSmartTriggers() { return client.listTriggers().await().atMost(client.getTimeout()); diff --git a/src/main/java/io/cryostat/targets/AgentRestClient.java b/src/main/java/io/cryostat/targets/AgentRestClient.java index e6c360e72..2fedf816f 100644 --- a/src/main/java/io/cryostat/targets/AgentRestClient.java +++ b/src/main/java/io/cryostat/targets/AgentRestClient.java @@ -54,6 +54,11 @@ interface AgentRestClient { @Produces(MediaType.APPLICATION_OCTET_STREAM) Uni invokeMBeanOperation(InputStream payload); + @Path("/mbean-query/") + @GET + @Produces(MediaType.APPLICATION_JSON) + Uni queryMbeanAttributes(); + @Path("/smart-triggers/") @POST Uni addTriggers(InputStream payload);