Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(agent): enable Agent HTTP communications #342

Merged
merged 47 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7549697
tmp
andrewazores Mar 26, 2024
a394b18
remove unused methods
andrewazores Mar 26, 2024
708f00f
tmp debugging
andrewazores Mar 27, 2024
a6bf9b7
refactor, cleanup
andrewazores Mar 27, 2024
69b1833
credentials lookup caching
andrewazores Mar 27, 2024
f73fba5
cleanup
andrewazores Mar 27, 2024
45638ee
remove conflicting update hooks
andrewazores Mar 27, 2024
e59fb6a
debug logging cleanup
andrewazores Mar 27, 2024
f26ac08
refactor
andrewazores Mar 27, 2024
c949180
fix test injection
andrewazores Mar 27, 2024
8a097ee
tmp
andrewazores Mar 27, 2024
6661aa5
fix persistence context bug which broke delayed-stop and archive-on-s…
andrewazores Mar 27, 2024
7162465
error handling
andrewazores Mar 27, 2024
0b49979
replace ScheduledExecutorService with quartz
andrewazores Mar 27, 2024
1812f9f
refactor, handle case where no target matches url
andrewazores Mar 28, 2024
09fd1d4
refactoring
andrewazores Mar 28, 2024
65289f6
threading, error handling
andrewazores Mar 28, 2024
51c12ce
refactor
andrewazores Mar 28, 2024
532347f
connection streaming bugfix
andrewazores Mar 28, 2024
96f2ed4
handle externally-created recordings
andrewazores Mar 28, 2024
3b4f8dc
spotbugs
andrewazores Mar 28, 2024
3cb2795
cleaner messagingserver shutdown
andrewazores Mar 28, 2024
00046e7
don't emit notifications when externally-created (or cross-url, for d…
andrewazores Mar 28, 2024
362b4e7
avoid NoResultException if 'archive on stop' recording is already del…
andrewazores Mar 28, 2024
1883867
remove unnecessary Blocking annotation
andrewazores Apr 2, 2024
9a7c037
refactor, move persistence interactions out of worker thread executio…
andrewazores Apr 2, 2024
0ea1fb5
update log level
andrewazores Apr 2, 2024
12ea9a3
delete activerecording records which are no longer observed in the ta…
andrewazores Apr 5, 2024
5566f76
refactor
andrewazores Apr 5, 2024
1659dea
add FIXME
andrewazores Apr 5, 2024
f71f342
fixup! remove conflicting update hooks
andrewazores Apr 9, 2024
b501342
rebase fixup, see #353
andrewazores Apr 9, 2024
d558197
tolerate 404 on agent recording deletion
andrewazores Apr 16, 2024
925e467
rebase fix
andrewazores Apr 16, 2024
66e1a8d
Merge branch 'main' into agent-client
andrewazores Apr 18, 2024
8807e5f
Merge branch 'main' into agent-client
andrewazores Apr 18, 2024
4480e13
fix up integration with graphql
andrewazores Apr 18, 2024
e26a65b
Merge remote-tracking branch 'upstream/main' into agent-client
andrewazores Apr 19, 2024
9da61a0
add query for node's target ID
andrewazores Apr 19, 2024
060331a
ensure response fields are initialized to empty values, not nulls
andrewazores Apr 19, 2024
d605228
handle unlikely possibility that a Target has no associated discovery…
andrewazores Apr 19, 2024
3e34cc6
update schemas
andrewazores Apr 19, 2024
1ab1f8b
Merge remote-tracking branch 'upstream/main' into agent-client
andrewazores Apr 19, 2024
3b11327
Merge branch 'main' into agent-client
andrewazores Apr 19, 2024
cc66963
Merge branch 'main' into agent-client
andrewazores Apr 22, 2024
805b564
Merge branch 'main' into agent-client
andrewazores Apr 23, 2024
d335777
disable self JMX auth for testing
andrewazores Apr 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading