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

fix(db): correct use of Transactional annotation #415

Merged
merged 34 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
aae5d7d
fix(db): correct use of Transactional annotation
andrewazores Apr 25, 2024
f08e5ba
fixup! fix(db): correct use of Transactional annotation
andrewazores Apr 25, 2024
0f8c9fe
more cleanup
andrewazores Apr 29, 2024
42ba603
commit tx
andrewazores Apr 30, 2024
f034d28
fixup! commit tx
andrewazores Apr 30, 2024
a4c05b4
JDP discovery uses eventbus
andrewazores May 3, 2024
f72e062
add missing node parent references
andrewazores Apr 24, 2024
d9afbe9
correct discovery plugin deletion to use db cascade
andrewazores Apr 24, 2024
8cb4ee0
don't start informers if not enabled
andrewazores Apr 24, 2024
5fb3278
delete periodic prune job if plugin is no longer registered
andrewazores Apr 24, 2024
4f57830
fixup! correct discovery plugin deletion to use db cascade
andrewazores Apr 24, 2024
2263d30
discoveryplugins require a stored credential reference
andrewazores Apr 24, 2024
b00a239
fixup! discoveryplugins require a stored credential reference
andrewazores Apr 24, 2024
c12e1ad
avoid testing match expressions for Agent HTTP connection credentials…
andrewazores Apr 24, 2024
0936d8d
Revert "avoid testing match expressions for Agent HTTP connection cre…
andrewazores Apr 24, 2024
7d33ec9
fixup! fixup! discoveryplugins require a stored credential reference
andrewazores Apr 24, 2024
fe6cce6
ping existing plugin if re-registration for same callback received
andrewazores Apr 24, 2024
c2a0272
fixup! ping existing plugin if re-registration for same callback rece…
andrewazores Apr 24, 2024
4b98ab6
rebase fix
andrewazores Apr 24, 2024
7f5cd09
only set credential if not already set
andrewazores Apr 25, 2024
9226602
add eager blank checks
andrewazores Apr 25, 2024
2af6104
set callback explicitly nullable
andrewazores Apr 25, 2024
024f5ef
increase log level
andrewazores Apr 25, 2024
80a8078
upgrade db-viewer version
andrewazores Apr 25, 2024
c88b89e
update schema
andrewazores Apr 25, 2024
cf554e7
spotbugs
andrewazores Apr 25, 2024
1ae297f
update schema
andrewazores Apr 25, 2024
d23fb4c
remodel optional discoveryplugin<->credential relation
andrewazores Apr 25, 2024
c308f80
don't require realm and callback in POST, plugin may not supply this …
andrewazores Apr 25, 2024
37e39db
correct callback comparison
andrewazores Apr 25, 2024
f6b173c
cleanup
andrewazores Apr 29, 2024
a2765f5
enable Agent dual-registration case for testing
andrewazores Apr 29, 2024
52159d2
fix rule execution and pruning
andrewazores May 7, 2024
a356db0
typo
andrewazores May 8, 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: 1 addition & 1 deletion compose/cryostat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ services:
io.cryostat.jmxPort: "0"
io.cryostat.jmxUrl: "service:jmx:rmi:///jndi/rmi://localhost:0/jmxrmi"
environment:
QUARKUS_LOG_LEVEL: TRACE
QUARKUS_LOG_LEVEL: ALL
QUARKUS_HTTP_HOST: "cryostat"
QUARKUS_HTTP_PORT: ${CRYOSTAT_HTTP_PORT}
QUARKUS_HIBERNATE_ORM_LOG_SQL: "true"
Expand Down
2 changes: 1 addition & 1 deletion compose/db-viewer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ services:
depends_on:
db:
condition: service_healthy
image: ${DB_VIEWER_IMAGE:-docker.io/dpage/pgadmin4:7}
image: ${DB_VIEWER_IMAGE:-docker.io/dpage/pgadmin4:8}
hostname: db-viewer
ports:
- "8989:8989"
Expand Down
13 changes: 12 additions & 1 deletion compose/sample-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,18 @@ services:
expose:
- "9977"
environment:
JAVA_OPTS_APPEND: "-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -javaagent:/deployments/app/cryostat-agent.jar"
JAVA_OPTS_APPEND: >-
-Dquarkus.http.host=0.0.0.0
-Djava.util.logging.manager=org.jboss.logmanager.LogManager
-javaagent:/deployments/app/cryostat-agent.jar
-Dcom.sun.management.jmxremote.autodiscovery=false
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=22222
-Dcom.sun.management.jmxremote.rmi.port=22222
-Djava.rmi.server.hostname=quarkus-test-agent
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.local.only=false
QUARKUS_HTTP_PORT: 10010
ORG_ACME_CRYOSTATSERVICE_ENABLED: "false"
CRYOSTAT_AGENT_APP_NAME: quarkus-test-agent
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/io/cryostat/credentials/Credential.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
*/
package io.cryostat.credentials;

import io.cryostat.discovery.DiscoveryPlugin;
import io.cryostat.expressions.MatchExpression;
import io.cryostat.ws.MessagingServer;
import io.cryostat.ws.Notification;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.persistence.CascadeType;
Expand Down Expand Up @@ -68,6 +71,16 @@ public class Credential extends PanacheEntity {
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public String password;

@OneToOne(
optional = true,
fetch = FetchType.LAZY,
mappedBy = "credential",
cascade = CascadeType.REMOVE)
@JoinColumn(name = "discoveryPlugin_id")
@JsonIgnore
@Nullable
public DiscoveryPlugin discoveryPlugin;

@ApplicationScoped
static class Listener {
@Inject EventBus bus;
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/io/cryostat/credentials/Credentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ public RestResponse<Void> create(
@RolesAllowed("write")
@Path("/{id}")
public void delete(@RestPath long id) {
Credential credential = Credential.find("id", id).singleResult();
credential.delete();
Credential.find("id", id).singleResult().delete();
}

static Map<String, Object> notificationResult(Credential credential) throws ScriptException {
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/io/cryostat/discovery/CustomDiscovery.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
import io.cryostat.targets.Target.Annotations;
import io.cryostat.targets.TargetConnectionManager;

import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.runtime.StartupEvent;
import io.smallrye.common.annotation.Blocking;
import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
Expand Down Expand Up @@ -128,13 +128,15 @@ public Response create(
return doV2Create(target, Optional.ofNullable(credential), dryrun, storeCredentials);
}

@Transactional
@Blocking
Response doV2Create(
Target target,
Optional<Credential> credential,
boolean dryrun,
boolean storeCredentials) {
var beginTx = !QuarkusTransaction.isActive();
if (beginTx) {
QuarkusTransaction.begin();
andrewazores marked this conversation as resolved.
Show resolved Hide resolved
}
try {
target.connectUrl = sanitizeConnectUrl(target.connectUrl.toString());

Expand Down Expand Up @@ -178,10 +180,17 @@ Response doV2Create(
node.persist();
realm.persist();

if (beginTx) {
QuarkusTransaction.commit();
}

return Response.created(URI.create("/api/v3/targets/" + target.id))
.entity(V2Response.json(Response.Status.CREATED, target))
.build();
} catch (Exception e) {
// roll back regardless of whether we initiated this database transaction or a caller
// did
QuarkusTransaction.rollback();
if (ExceptionUtils.indexOfType(e, ConstraintViolationException.class) >= 0) {
logger.warn("Invalid target definition", e);
return Response.status(Response.Status.BAD_REQUEST.getStatusCode())
Expand Down
75 changes: 45 additions & 30 deletions src/main/java/io/cryostat/discovery/Discovery.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,9 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import io.cryostat.credentials.Credential;
import io.cryostat.discovery.DiscoveryPlugin.PluginCallback;
import io.cryostat.targets.TargetConnectionManager;

Expand All @@ -43,6 +41,7 @@
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jwt.proc.BadJWTException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import io.vertx.core.json.JsonObject;
Expand All @@ -64,6 +63,7 @@
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriBuilder;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
Expand Down Expand Up @@ -180,6 +180,7 @@ public RestResponse<Void> checkRegistration(
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RolesAllowed("write")
@SuppressFBWarnings("DLS_DEAD_LOCAL_STORE")
public Response register(@Context RoutingContext ctx, JsonObject body)
throws URISyntaxException,
JOSEException,
Expand All @@ -192,6 +193,7 @@ public Response register(@Context RoutingContext ctx, JsonObject body)
String priorToken = body.getString("token");
String realmName = body.getString("realm");
URI callbackUri = new URI(body.getString("callback"));
URI unauthCallback = UriBuilder.fromUri(callbackUri).userInfo(null).build();

// TODO apply URI range validation to the remote address
InetAddress remoteAddress = getRemoteAddress(ctx);
Expand All @@ -205,22 +207,46 @@ public Response register(@Context RoutingContext ctx, JsonObject body)
if (!Objects.equals(plugin.realm.name, realmName)) {
throw new ForbiddenException();
}
if (!Objects.equals(plugin.callback, callbackUri)) {
if (!Objects.equals(plugin.callback, unauthCallback)) {
throw new BadRequestException();
}
location = jwtFactory.getPluginLocation(plugin);
jwtFactory.parseDiscoveryPluginJwt(plugin, priorToken, location, remoteAddress, false);
} else {
// check if a plugin record with the same callback already exists. If it does, ping it:
// if it's still there reject this request as a duplicate, otherwise delete the previous
// record and accept this new one as a replacement
DiscoveryPlugin.<DiscoveryPlugin>find("callback", unauthCallback)
.singleResultOptional()
.ifPresent(
p -> {
try {
var cb = PluginCallback.create(p);
cb.ping();
throw new IllegalArgumentException(
String.format(
"Plugin with callback %s already exists and is"
+ " still reachable",
unauthCallback));
} catch (Exception e) {
logger.error(e);
p.delete();
}
});

// new plugin registration
plugin = new DiscoveryPlugin();
plugin.callback = callbackUri;
plugin.realm =
DiscoveryNode.environment(
requireNonBlank(realmName, "realm"), BaseNodeType.REALM);
plugin.builtin = false;
plugin.persist();

DiscoveryNode.getUniverse().children.add(plugin.realm);
var universe = DiscoveryNode.getUniverse();
plugin.realm.parent = universe;
plugin.persist();
universe.children.add(plugin.realm);
universe.persist();

location = jwtFactory.getPluginLocation(plugin);

Expand Down Expand Up @@ -293,15 +319,15 @@ public Map<String, Map<String, String>> publish(
DiscoveryPlugin plugin = DiscoveryPlugin.find("id", id).singleResult();
jwtValidator.validateJwt(ctx, plugin, token, true);
plugin.realm.children.clear();
plugin.persist();
plugin.realm.children.addAll(body);
body.forEach(
b -> {
if (b.target != null) {
b.target.discoveryNode = b;
}
b.persist();
});
for (var b : body) {
if (b.target != null) {
b.target.discoveryNode = b;
b.target.discoveryNode.parent = plugin.realm;
b.parent = plugin.realm;
}
b.persist();
}
plugin.persist();

return Map.of(
Expand Down Expand Up @@ -338,10 +364,7 @@ public Map<String, Map<String, String>> deregister(
scheduler.deleteJob(key);
}

plugin.realm.delete();
plugin.delete();
getStoredCredential(plugin).ifPresent(Credential::delete);
DiscoveryNode.getUniverse().children.remove(plugin.realm);
return Map.of(
"meta",
Map.of(
Expand Down Expand Up @@ -374,17 +397,11 @@ public DiscoveryPlugin getPlugin(@RestPath UUID id) throws JsonProcessingExcepti
return DiscoveryPlugin.find("id", id).singleResult();
}

Optional<Credential> getStoredCredential(DiscoveryPlugin plugin) {
return new DiscoveryPlugin.PluginCallback.DiscoveryPluginAuthorizationHeaderFactory(plugin)
.getCredential();
}

@SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
static class RefreshPluginJob implements Job {
@Inject Logger logger;

@Override
@Transactional
@SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public void execute(JobExecutionContext context) throws JobExecutionException {
DiscoveryPlugin plugin = null;
try {
Expand All @@ -407,14 +424,12 @@ public void execute(JobExecutionContext context) throws JobExecutionException {
if (plugin != null) {
logger.debugv(
e, "Pruned discovery plugin: {0} @ {1}", plugin.realm, plugin.callback);
plugin.realm.delete();
plugin.delete();
new DiscoveryPlugin.PluginCallback.DiscoveryPluginAuthorizationHeaderFactory(
plugin)
.getCredential()
.ifPresent(Credential::delete);
QuarkusTransaction.requiringNew().run(plugin::delete);
} else {
var ex = new JobExecutionException(e);
ex.setUnscheduleFiringTrigger(true);
throw ex;
}
throw new JobExecutionException(e);
}
}
}
Expand Down
41 changes: 25 additions & 16 deletions src/main/java/io/cryostat/discovery/DiscoveryNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonView;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.quarkus.narayana.jta.QuarkusTransaction;
import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
Expand Down Expand Up @@ -130,25 +131,33 @@ public static List<DiscoveryNode> findAllByNodeType(NodeType nodeType) {
}

public static DiscoveryNode environment(String name, NodeType nodeType) {
DiscoveryNode node = new DiscoveryNode();
node.name = name;
node.nodeType = nodeType.getKind();
node.labels = new HashMap<>();
node.children = new ArrayList<>();
node.target = null;
node.persist();
return node;
return QuarkusTransaction.joiningExisting()
.call(
() -> {
DiscoveryNode node = new DiscoveryNode();
node.name = name;
node.nodeType = nodeType.getKind();
node.labels = new HashMap<>();
node.children = new ArrayList<>();
node.target = null;
node.persist();
return node;
});
}

public static DiscoveryNode target(Target target, NodeType nodeType) {
DiscoveryNode node = new DiscoveryNode();
node.name = target.connectUrl.toString();
node.nodeType = nodeType.getKind();
node.labels = new HashMap<>(target.labels);
node.children = null;
node.target = target;
node.persist();
return node;
return QuarkusTransaction.joiningExisting()
.call(
() -> {
DiscoveryNode node = new DiscoveryNode();
node.name = target.connectUrl.toString();
node.nodeType = nodeType.getKind();
node.labels = new HashMap<>(target.labels);
node.children = null;
node.target = target;
node.persist();
return node;
});
}

@Override
Expand Down
Loading
Loading