Skip to content

Commit

Permalink
feat(api): remote readonly events access (#80)
Browse files Browse the repository at this point in the history
* add event types fetching context

Signed-off-by: Andrew Azores <aazores@redhat.com>

* implement event templates query

* JDK11 compat fixup

* add 'deflate' response body filter

Signed-off-by: Andrew Azores <aazores@redhat.com>

* refactor cleanup

* fixup! refactor cleanup

Signed-off-by: Andrew Azores <aazores@redhat.com>

---------

Signed-off-by: Andrew Azores <aazores@redhat.com>
  • Loading branch information
andrewazores authored Mar 20, 2023
1 parent b2bc9ea commit 5a9d4c4
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 1 deletion.
1 change: 0 additions & 1 deletion src/main/java/io/cryostat/agent/MainModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ public static HttpClient provideHttpClient(
}

@Provides
@Singleton
public static ObjectMapper provideObjectMapper() {
return new ObjectMapper();
}
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/io/cryostat/agent/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@
import java.time.Duration;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors;
import java.util.zip.DeflaterOutputStream;

import io.cryostat.agent.remote.RemoteContext;

Expand Down Expand Up @@ -77,6 +80,7 @@ class WebServer {

private final AgentAuthenticator agentAuthenticator;
private final RequestLoggingFilter requestLoggingFilter;
private final CompressionFilter compressionFilter;

WebServer(
Lazy<Set<RemoteContext>> remoteContexts,
Expand All @@ -95,6 +99,7 @@ class WebServer {

this.agentAuthenticator = new AgentAuthenticator();
this.requestLoggingFilter = new RequestLoggingFilter();
this.compressionFilter = new CompressionFilter();
}

void start() throws IOException, NoSuchAlgorithmException {
Expand All @@ -114,6 +119,7 @@ void start() throws IOException, NoSuchAlgorithmException {
HttpContext ctx = this.http.createContext(rc.path(), rc::handle);
ctx.setAuthenticator(agentAuthenticator);
ctx.getFilters().add(requestLoggingFilter);
ctx.getFilters().add(compressionFilter);
});

this.http.start();
Expand Down Expand Up @@ -164,6 +170,54 @@ public String description() {
}
}

private class CompressionFilter extends Filter {

@Override
public void doFilter(HttpExchange exchange, Chain chain) throws IOException {
List<String> requestedEncodings =
exchange.getRequestHeaders().getOrDefault("Accept-Encoding", List.of()).stream()
.map(raw -> raw.replaceAll("\\s", ""))
.map(raw -> raw.split(","))
.map(Arrays::asList)
.flatMap(List::stream)
.collect(Collectors.toList());
String negotiatedEncoding = null;
priority:
for (String encoding : requestedEncodings) {
switch (encoding) {
case "deflate":
negotiatedEncoding = encoding;
exchange.setStreams(
exchange.getRequestBody(),
new DeflaterOutputStream(exchange.getResponseBody()));
break priority;
// TODO gzip encoding breaks communication with the server, need to
// determine why and re-enable this
// case "gzip":
// actualEncoding = requestedEncoding;
// exchange.setStreams(
// exchange.getRequestBody(),
// new GZIPOutputStream(exchange.getResponseBody()));
// break priority;
default:
break;
}
}
if (negotiatedEncoding == null) {
log.info("Using no encoding");
} else {
log.info("Using '{}' encoding", negotiatedEncoding);
exchange.getResponseHeaders().put("Content-Encoding", List.of(negotiatedEncoding));
}
chain.doFilter(exchange);
}

@Override
public String description() {
return "responseCompression";
}
}

private class PingContext implements RemoteContext {

@Override
Expand Down
99 changes: 99 additions & 0 deletions src/main/java/io/cryostat/agent/remote/EventTemplatesContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright The Cryostat Authors
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or data
* (collectively the "Software"), free of charge and under any and all copyright
* rights in the Software, and any and all patent rights owned or freely
* licensable by each licensor hereunder covering either (i) the unmodified
* Software as contributed to or provided by such licensor, or (ii) the Larger
* Works (as defined below), to deal in both
*
* (a) the Software, and
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software (each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
* The above copyright notice and either this complete permission notice or at
* a minimum a reference to the UPL must be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.cryostat.agent.remote;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Inject;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;
import jdk.management.jfr.ConfigurationInfo;
import jdk.management.jfr.FlightRecorderMXBean;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class EventTemplatesContext implements RemoteContext {

private final Logger log = LoggerFactory.getLogger(getClass());
private final ObjectMapper mapper;

@Inject
EventTemplatesContext(ObjectMapper mapper) {
this.mapper = mapper;
}

@Override
public String path() {
return "/event-templates";
}

@Override
public void handle(HttpExchange exchange) throws IOException {
String mtd = exchange.getRequestMethod();
switch (mtd) {
case "GET":
try {
exchange.sendResponseHeaders(HttpStatus.SC_OK, 0);
try (OutputStream response = exchange.getResponseBody()) {
FlightRecorderMXBean bean =
ManagementFactory.getPlatformMXBean(FlightRecorderMXBean.class);
List<String> xmlTexts =
bean.getConfigurations().stream()
.map(ConfigurationInfo::getContents)
.collect(Collectors.toList());
mapper.writeValue(response, xmlTexts);
}
} catch (Exception e) {
log.error("events serialization failure", e);
} finally {
exchange.close();
}
break;
default:
exchange.sendResponseHeaders(HttpStatus.SC_NOT_FOUND, -1);
exchange.close();
break;
}
}
}
139 changes: 139 additions & 0 deletions src/main/java/io/cryostat/agent/remote/EventTypesContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright The Cryostat Authors
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or data
* (collectively the "Software"), free of charge and under any and all copyright
* rights in the Software, and any and all patent rights owned or freely
* licensable by each licensor hereunder covering either (i) the unmodified
* Software as contributed to or provided by such licensor, or (ii) the Larger
* Works (as defined below), to deal in both
*
* (a) the Software, and
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software (each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
* The above copyright notice and either this complete permission notice or at
* a minimum a reference to the UPL must be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.cryostat.agent.remote;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.inject.Inject;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jdk.jfr.EventType;
import jdk.jfr.FlightRecorder;
import jdk.jfr.SettingDescriptor;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class EventTypesContext implements RemoteContext {

private final Logger log = LoggerFactory.getLogger(getClass());
private final ObjectMapper mapper;

@Inject
EventTypesContext(ObjectMapper mapper) {
this.mapper = mapper;
}

@Override
public String path() {
return "/event-types";
}

@Override
public void handle(HttpExchange exchange) throws IOException {
String mtd = exchange.getRequestMethod();
switch (mtd) {
case "GET":
try {
List<EventInfo> events = getEventTypes();
exchange.sendResponseHeaders(HttpStatus.SC_OK, 0);
try (OutputStream response = exchange.getResponseBody()) {
mapper.writeValue(response, events);
}
} catch (Exception e) {
log.error("events serialization failure", e);
} finally {
exchange.close();
}
break;
default:
exchange.sendResponseHeaders(HttpStatus.SC_NOT_FOUND, -1);
exchange.close();
break;
}
}

private List<EventInfo> getEventTypes() {
return FlightRecorder.getFlightRecorder().getEventTypes().stream()
.map(
evt -> {
EventInfo evtInfo = new EventInfo(evt);
evtInfo.settings.addAll(
evt.getSettingDescriptors().stream()
.map(SettingInfo::new)
.collect(Collectors.toList()));
return evtInfo;
})
.collect(Collectors.toList());
}

@SuppressFBWarnings(value = "URF_UNREAD_FIELD")
private static class EventInfo {

public final String name;
public final String label;
public final String description;
public final List<String> categories;
public final List<SettingInfo> settings = new ArrayList<>();

EventInfo(EventType evt) {
this.name = evt.getName();
this.label = evt.getLabel();
this.description = evt.getDescription();
this.categories = evt.getCategoryNames();
}
}

@SuppressFBWarnings(value = "URF_UNREAD_FIELD")
private static class SettingInfo {

public final String name;
public final String defaultValue;

SettingInfo(SettingDescriptor desc) {
this.name = desc.getName();
this.defaultValue = desc.getDefaultValue();
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/io/cryostat/agent/remote/RemoteModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,12 @@ public abstract class RemoteModule {
@Binds
@IntoSet
abstract RemoteContext bindMBeanContext(MBeanContext ctx);

@Binds
@IntoSet
abstract RemoteContext bindEventTypesContext(EventTypesContext ctx);

@Binds
@IntoSet
abstract RemoteContext bindEventTemplatesContext(EventTemplatesContext ctx);
}

0 comments on commit 5a9d4c4

Please sign in to comment.