Skip to content

Commit

Permalink
feat(agent): enable Agent HTTP communications (#342)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewazores authored Apr 23, 2024
1 parent 1071e70 commit 05060e5
Show file tree
Hide file tree
Showing 31 changed files with 1,341 additions and 591 deletions.
2 changes: 2 additions & 0 deletions compose/cryostat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ services:
io.cryostat.jmxPort: "0"
io.cryostat.jmxUrl: "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"
environment:
QUARKUS_LOG_LEVEL: TRACE
QUARKUS_HTTP_HOST: "cryostat"
QUARKUS_HTTP_PORT: ${CRYOSTAT_HTTP_PORT}
QUARKUS_HIBERNATE_ORM_LOG_SQL: "true"
CRYOSTAT_DISABLE_JMX_AUTH: "true"
CRYOSTAT_DISCOVERY_JDP_ENABLED: ${CRYOSTAT_DISCOVERY_JDP_ENABLED:-true}
CRYOSTAT_DISCOVERY_PODMAN_ENABLED: ${CRYOSTAT_DISCOVERY_PODMAN_ENABLED:-true}
CRYOSTAT_DISCOVERY_DOCKER_ENABLED: ${CRYOSTAT_DISCOVERY_DOCKER_ENABLED:-true}
Expand Down
1 change: 1 addition & 0 deletions compose/sample-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ services:
CRYOSTAT_AGENT_HARVESTER_MAX_FILES: 3
CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_AGE_MS: 60000
CRYOSTAT_AGENT_HARVESTER_EXIT_MAX_SIZE_B: 153600 # "$(echo 1024*150 | bc)"
CRYOSTAT_AGENT_API_WRITES_ENABLED: "true"
restart: always
healthcheck:
test: curl --fail http://localhost:10010 || exit 1
Expand Down
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

<org.apache.commons.codec.version>1.16.1</org.apache.commons.codec.version>
<org.apache.commons.io.version>2.13.0</org.apache.commons.io.version>
<org.apache.commons.collections.version>4.4</org.apache.commons.collections.version>
<org.apache.httpcomponents.version>5.2.1</org.apache.httpcomponents.version>
<org.apache.commons.lang3.version>3.13.0</org.apache.commons.lang3.version>
<org.apache.commons.validator.version>1.7</org.apache.commons.validator.version>
Expand Down Expand Up @@ -164,6 +165,11 @@
<artifactId>commons-io</artifactId>
<version>${org.apache.commons.io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${org.apache.commons.collections.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
Expand Down
1 change: 1 addition & 0 deletions schema/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2278,6 +2278,7 @@ paths:
nullable: true
type: string
restart:
deprecated: true
nullable: true
type: boolean
toDisk:
Expand Down
1 change: 1 addition & 0 deletions schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ input DiscoveryNodeFilterInput {
name: String
names: [String]
nodeTypes: [String]
targetIds: [BigInteger]
}

input Entry_String_StringInput {
Expand Down
Binary file added src/main/docker/include/jmc-agent.jar
Binary file not shown.
3 changes: 2 additions & 1 deletion src/main/java/io/cryostat/JsonRequestFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.MediaType;
Expand All @@ -45,7 +46,7 @@ public class JsonRequestFilter implements ContainerRequestFilter {
"/api/v3/graphql");

private final Map<String, Pattern> compiledPatterns = new HashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
@Inject ObjectMapper objectMapper;

@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Expand Down
82 changes: 82 additions & 0 deletions src/main/java/io/cryostat/credentials/CredentialsFinder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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.credentials;

import java.net.URI;
import java.util.Optional;

import io.cryostat.expressions.MatchExpressionEvaluator;
import io.cryostat.targets.Target;
import io.cryostat.targets.Target.EventKind;
import io.cryostat.targets.Target.TargetDiscovery;

import io.quarkus.vertx.ConsumeEvent;
import io.smallrye.common.annotation.Blocking;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.jboss.logging.Logger;
import org.projectnessie.cel.tools.ScriptException;

@ApplicationScoped
public class CredentialsFinder {

@Inject MatchExpressionEvaluator expressionEvaluator;
@Inject Logger logger;

private final BidiMap<Target, Credential> cache = new DualHashBidiMap<>();

@ConsumeEvent(Credential.CREDENTIALS_DELETED)
void onCredentialsDeleted(Credential credential) {
cache.removeValue(credential);
}

@ConsumeEvent(Target.TARGET_JVM_DISCOVERY)
void onMessage(TargetDiscovery event) {
if (EventKind.LOST.equals(event.kind())) {
cache.remove(event.serviceRef());
}
}

@Blocking
public Optional<Credential> getCredentialsForTarget(Target target) {
return Optional.ofNullable(
cache.computeIfAbsent(
target,
t ->
Credential.<Credential>listAll().stream()
.filter(
c -> {
try {
return expressionEvaluator.applies(
c.matchExpression, t);
} catch (ScriptException e) {
logger.error(e);
return false;
}
})
.findFirst()
.orElse(null)));
}

@Blocking
public Optional<Credential> getCredentialsForConnectUrl(URI connectUrl) {
return Target.find("connectUrl", connectUrl)
.<Target>singleResultOptional()
.flatMap(this::getCredentialsForTarget);
}
}
17 changes: 7 additions & 10 deletions src/main/java/io/cryostat/discovery/ContainerDiscovery.java
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,14 @@ private void doContainerListRequest(Consumer<List<ContainerSpec>> successHandler
item.body(),
new TypeReference<List<ContainerSpec>>() {}));
} catch (JsonProcessingException e) {
logger.error("Json processing error");
logger.error("Json processing error", e);
}
},
failure -> {
logger.error(
String.format("%s API request failed", getRealm()),
failure);
logger.errorv(failure, "{0} API request failed", getRealm());
});
} catch (JsonProcessingException e) {
logger.error("Json processing error");
logger.error("Json processing error", e);
}
}

Expand All @@ -279,13 +277,12 @@ private CompletableFuture<ContainerDetails> doContainerInspectRequest(ContainerS
result.complete(
mapper.readValue(item.body(), ContainerDetails.class));
} catch (JsonProcessingException e) {
logger.error("Json processing error");
logger.error("Json processing error", e);
result.completeExceptionally(e);
}
},
failure -> {
logger.error(
String.format("%s API request failed", getRealm()), failure);
logger.errorv(failure, "{0} API request failed", getRealm());
result.completeExceptionally(failure);
});
return result;
Expand Down Expand Up @@ -322,7 +319,7 @@ public void handleContainerEvent(ContainerSpec desc, EventKind evtKind) {
.Hostname;
} catch (InterruptedException | TimeoutException | ExecutionException e) {
containers.remove(desc);
logger.warn(String.format("Invalid %s target observed", getRealm()), e);
logger.warnv(e, "Invalid {0} target observed", getRealm());
return;
}
}
Expand All @@ -331,7 +328,7 @@ public void handleContainerEvent(ContainerSpec desc, EventKind evtKind) {
connectUrl = URI.create(serviceUrl.toString());
} catch (MalformedURLException | URISyntaxException e) {
containers.remove(desc);
logger.warn(String.format("Invalid %s target observed", getRealm()), e);
logger.warnv(e, "Invalid {0} target observed", getRealm());
return;
}

Expand Down
7 changes: 6 additions & 1 deletion src/main/java/io/cryostat/discovery/CustomDiscovery.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import io.cryostat.ConfigProperties;
import io.cryostat.V2Response;
import io.cryostat.credentials.Credential;
import io.cryostat.expressions.MatchExpression;
Expand All @@ -49,6 +50,7 @@
import jakarta.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.exception.ConstraintViolationException;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.RestForm;
Expand All @@ -68,6 +70,9 @@ public class CustomDiscovery {
@Inject EventBus bus;
@Inject TargetConnectionManager connectionManager;

@ConfigProperty(name = ConfigProperties.CONNECTIONS_FAILED_TIMEOUT)
Duration timeout;

@Transactional
void onStart(@Observes StartupEvent evt) {
DiscoveryNode universe = DiscoveryNode.getUniverse();
Expand Down Expand Up @@ -140,7 +145,7 @@ Response doV2Create(
credential,
conn -> conn.getJvmIdentifier().getHash())
.await()
.atMost(Duration.ofSeconds(10));
.atMost(timeout);
} catch (Exception e) {
logger.error("Target connection failed", e);
return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/io/cryostat/discovery/Discovery.java
Original file line number Diff line number Diff line change
Expand Up @@ -397,17 +397,17 @@ public void execute(JobExecutionContext context) throws JobExecutionException {
var cb = PluginCallback.create(plugin);
if (refresh) {
cb.refresh();
logger.infov(
logger.debugv(
"Refreshed discovery plugin: {0} @ {1}", plugin.realm, plugin.callback);
} else {
cb.ping();
logger.infov(
logger.debugv(
"Retained discovery plugin: {0} @ {1}", plugin.realm, plugin.callback);
}
} catch (Exception e) {
if (plugin != null) {
logger.infov(
"Pruned discovery plugin: {0} @ {1}", plugin.realm, plugin.callback);
logger.debugv(
e, "Pruned discovery plugin: {0} @ {1}", plugin.realm, plugin.callback);
plugin.realm.delete();
plugin.delete();
new DiscoveryPlugin.PluginCallback.DiscoveryPluginAuthorizationHeaderFactory(
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/io/cryostat/discovery/DiscoveryPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public void prePersist(DiscoveryPlugin plugin) {
return;
}
if (plugin.callback == null) {
plugin.realm.delete();
plugin.delete();
throw new IllegalArgumentException();
}
try {
Expand All @@ -97,8 +99,12 @@ public void prePersist(DiscoveryPlugin plugin) {
"Registered discovery plugin: {0} @ {1}",
plugin.realm.name, plugin.callback);
} catch (URISyntaxException e) {
plugin.realm.delete();
plugin.delete();
throw new IllegalArgumentException(e);
} catch (Exception e) {
plugin.realm.delete();
plugin.delete();
logger.error("Discovery Plugin ping failed", e);
throw e;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,19 +100,16 @@ boolean load(String matchExpression, Target target) throws ScriptException {
}

void invalidate(String matchExpression) {
var cache = cacheManager.getCache(CACHE_NAME).orElseThrow();
// 0-index is important here. the argument order of the load() method determines the
// composite key order
cacheManager
.getCache(CACHE_NAME)
.ifPresent(
c ->
c.invalidateIf(
k ->
Objects.equals(
(String)
((CompositeCacheKey) k)
.getKeyElements()[0],
matchExpression)));
cache.invalidateIf(
k ->
Objects.equals(
(String) ((CompositeCacheKey) k).getKeyElements()[0],
matchExpression))
.subscribe()
.with((v) -> {}, logger::warn);
}

public boolean applies(MatchExpression matchExpression, Target target) throws ScriptException {
Expand Down
35 changes: 14 additions & 21 deletions src/main/java/io/cryostat/graphql/ActiveRecordings.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public List<ArchivedRecording> archiveRecording(
.map(n -> n.target))
.flatMap(
t ->
t.activeRecordings.stream()
recordingHelper.listActiveRecordings(t).stream()
.filter(r -> recordings == null || recordings.test(r)))
.map(
recording -> {
Expand Down Expand Up @@ -148,7 +148,7 @@ public List<ActiveRecording> stopRecording(
.map(n -> n.target))
.flatMap(
t ->
t.activeRecordings.stream()
recordingHelper.listActiveRecordings(t).stream()
.filter(r -> recordings == null || recordings.test(r)))
.map(
recording -> {
Expand All @@ -172,23 +172,16 @@ public List<ActiveRecording> stopRecording(
+ " the subtrees of the discovery nodes matching the given filter")
public List<ActiveRecording> deleteRecording(
@NonNull DiscoveryNodeFilter nodes, @Nullable ActiveRecordingsFilter recordings) {
var activeRecordings =
DiscoveryNode.<DiscoveryNode>listAll().stream()
.filter(nodes)
.flatMap(
node ->
RootNode.recurseChildren(node, n -> n.target != null)
.stream()
.map(n -> n.target))
.flatMap(
t ->
t.activeRecordings.stream()
.filter(
r ->
recordings == null
|| recordings.test(r)))
.toList();
return activeRecordings.stream()
return DiscoveryNode.<DiscoveryNode>listAll().stream()
.filter(nodes)
.flatMap(
node ->
RootNode.recurseChildren(node, n -> n.target != null).stream()
.map(n -> n.target))
.flatMap(
t ->
recordingHelper.listActiveRecordings(t).stream()
.filter(r -> recordings == null || recordings.test(r)))
.map(
recording -> {
try {
Expand Down Expand Up @@ -261,7 +254,7 @@ public Uni<ActiveRecording> doSnapshot(@Source Target target) {
@Blocking
@Transactional
@Description("Stop the specified Flight Recording")
public Uni<ActiveRecording> doStop(@Source ActiveRecording recording) {
public Uni<ActiveRecording> doStop(@Source ActiveRecording recording) throws Exception {
var ar = ActiveRecording.<ActiveRecording>findById(recording.id);
return recordingHelper.stopRecording(ar);
}
Expand Down Expand Up @@ -358,7 +351,7 @@ public void setLabels(Map<String, String> labels) {

@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
public static class RecordingMetadata {
public @Nullable Map<String, String> labels;
public @Nullable Map<String, String> labels = new HashMap<>();
}

@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
Expand Down
Loading

0 comments on commit 05060e5

Please sign in to comment.