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(snapshot): Cleanup notification handling for snapshots #917

Merged
merged 14 commits into from
Apr 29, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ public void handleAuthenticated(RoutingContext ctx) throws Exception {
boolean verificationSuccessful = false;
try {
verificationSuccessful =
recordingTargetHelper.verifySnapshot(connectionDescriptor, snapshotName).get();
recordingTargetHelper
.verifySnapshot(connectionDescriptor, snapshotDescriptor)
.get();
} catch (ExecutionException e) {
handleExecutionException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ public IntermediateResponse<HyperlinkedSerializableRecordingDescriptor> handle(
boolean verificationSuccessful = false;
try {
verificationSuccessful =
recordingTargetHelper.verifySnapshot(connectionDescriptor, snapshotName).get();
recordingTargetHelper
.verifySnapshot(connectionDescriptor, snapshotDescriptor)
.get();
} catch (ExecutionException e) {
handleExecutionException(e);
}
Expand Down
68 changes: 41 additions & 27 deletions src/main/java/io/cryostat/recordings/RecordingTargetHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,19 @@

public class RecordingTargetHelper {

private static final String CREATE_NOTIFICATION_CATEGORY = "ActiveRecordingCreated";
private static final String CREATION_NOTIFICATION_CATEGORY = "ActiveRecordingCreated";
private static final String STOP_NOTIFICATION_CATEGORY = "ActiveRecordingStopped";
private static final String DELETION_NOTIFICATION_CATEGORY = "ActiveRecordingDeleted";
private static final String SNAPSHOT_CREATION_NOTIFICATION_CATEGORY = "SnapshotCreated";
private static final String SNAPSHOT_DELETION_NOTIFICATION_CATEGORY = "SnapshotDeleted";

private static final long TIMESTAMP_DRIFT_SAFEGUARD = 3_000L;

private static final Pattern TEMPLATE_PATTERN =
Pattern.compile("^template=([\\w]+)(?:,type=([\\w]+))?$");

private static final Pattern SNAPSHOT_NAME_PATTERN = Pattern.compile("^(snapshot\\-)([0-9]+)$");

private final TargetConnectionManager targetConnectionManager;
private final Lazy<WebServer> webServer;
private final EventOptionsBuilder.Factory eventOptionsBuilderFactory;
Expand Down Expand Up @@ -181,13 +186,7 @@ public IRecordingDescriptor startRecording(
webServer.get().getDownloadURL(connection, desc.getName()),
webServer.get().getReportURL(connection, desc.getName()),
metadata);
notificationFactory
.createBuilder()
.metaCategory(CREATE_NOTIFICATION_CATEGORY)
.metaType(HttpMimeType.JSON)
.message(Map.of("recording", linkedDesc, "target", targetId))
.build()
.send();
this.issueNotification(targetId, linkedDesc, CREATION_NOTIFICATION_CATEGORY);

Object fixedDuration =
recordingOptions.get(RecordingOptionsBuilder.KEY_DURATION);
Expand Down Expand Up @@ -284,7 +283,7 @@ public IRecordingDescriptor stopRecording(
d,
webServer.get().getDownloadURL(connection, d.getName()),
webServer.get().getReportURL(connection, d.getName()));
this.notifyRecordingStopped(targetId, linkedDesc);
this.issueNotification(targetId, linkedDesc, STOP_NOTIFICATION_CATEGORY);
return getDescriptorByName(connection, recordingName).get();
} else {
throw new RecordingNotFoundException(targetId, recordingName);
Expand Down Expand Up @@ -346,9 +345,18 @@ public Future<HyperlinkedSerializableRecordingDescriptor> createSnapshot(
}

public Future<Boolean> verifySnapshot(
ConnectionDescriptor connectionDescriptor, String snapshotName) {
ConnectionDescriptor connectionDescriptor,
HyperlinkedSerializableRecordingDescriptor snapshotDescriptor) {
return this.verifySnapshot(connectionDescriptor, snapshotDescriptor, true);
}

public Future<Boolean> verifySnapshot(
ConnectionDescriptor connectionDescriptor,
HyperlinkedSerializableRecordingDescriptor snapshotDescriptor,
boolean issueNotification) {
CompletableFuture<Boolean> future = new CompletableFuture<>();
try {
String snapshotName = snapshotDescriptor.getName();
Optional<InputStream> snapshotOptional =
this.getRecording(connectionDescriptor, snapshotName).get();
if (snapshotOptional.isEmpty()) {
Expand All @@ -360,6 +368,12 @@ public Future<Boolean> verifySnapshot(
this.deleteRecording(connectionDescriptor, snapshotName, false).get();
future.complete(false);
} else {
if (issueNotification) {
this.issueNotification(
connectionDescriptor.getTargetId(),
snapshotDescriptor,
SNAPSHOT_CREATION_NOTIFICATION_CATEGORY);
}
future.complete(true);
}
}
Expand Down Expand Up @@ -428,18 +442,13 @@ private Future<Void> deleteRecording(
recordingMetadataManager.deleteRecordingMetadataIfExists(
connectionDescriptor.getTargetId(), recordingName);
if (issueNotification) {
notificationFactory
.createBuilder()
.metaCategory(DELETION_NOTIFICATION_CATEGORY)
.metaType(HttpMimeType.JSON)
.message(
Map.of(
"recording",
linkedDesc,
"target",
connectionDescriptor.getTargetId()))
.build()
.send();
Matcher m = SNAPSHOT_NAME_PATTERN.matcher(recordingName);
String notificationCategory =
m.matches()
? SNAPSHOT_DELETION_NOTIFICATION_CATEGORY
: DELETION_NOTIFICATION_CATEGORY;
this.issueNotification(
targetId, linkedDesc, notificationCategory);
}
} else {
throw new RecordingNotFoundException(targetId, recordingName);
Expand All @@ -453,13 +462,15 @@ private Future<Void> deleteRecording(
return future;
}

private void notifyRecordingStopped(
String targetId, HyperlinkedSerializableRecordingDescriptor desc) {
private void issueNotification(
String targetId,
HyperlinkedSerializableRecordingDescriptor linkedDesc,
String notificationCategory) {
notificationFactory
.createBuilder()
.metaCategory(STOP_NOTIFICATION_CATEGORY)
.metaCategory(notificationCategory)
.metaType(HttpMimeType.JSON)
.message(Map.of("recording", desc, "target", targetId))
.message(Map.of("recording", linkedDesc, "target", targetId))
.build()
.send();
}
Expand Down Expand Up @@ -560,7 +571,10 @@ private void scheduleRecordingStopNotification(
.get()
.getReportURL(
connection, name));
this.notifyRecordingStopped(targetId, linkedDesc);
this.issueNotification(
targetId,
linkedDesc,
STOP_NOTIFICATION_CATEGORY);
}

return desc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ void shouldCreateSnapshot() throws Exception {
CompletableFuture<Boolean> future2 = Mockito.mock(CompletableFuture.class);
Mockito.when(
recordingTargetHelper.verifySnapshot(
Mockito.any(ConnectionDescriptor.class), Mockito.eq("snapshot-1")))
Mockito.any(ConnectionDescriptor.class),
Mockito.any(HyperlinkedSerializableRecordingDescriptor.class)))
.thenReturn(future2);
Mockito.when(future2.get()).thenReturn(true);

Expand Down Expand Up @@ -177,7 +178,8 @@ void shouldHandleSnapshotCreationExceptionDuringVerification() throws Exception
CompletableFuture<Boolean> future2 = Mockito.mock(CompletableFuture.class);
Mockito.when(
recordingTargetHelper.verifySnapshot(
Mockito.any(ConnectionDescriptor.class), Mockito.eq("snapshot-1")))
Mockito.any(ConnectionDescriptor.class),
Mockito.any(HyperlinkedSerializableRecordingDescriptor.class)))
.thenReturn(future2);
Mockito.when(future2.get())
.thenThrow(
Expand Down Expand Up @@ -215,7 +217,8 @@ void shouldHandleFailedSnapshotVerification() throws Exception {
CompletableFuture<Boolean> future2 = Mockito.mock(CompletableFuture.class);
Mockito.when(
recordingTargetHelper.verifySnapshot(
Mockito.any(ConnectionDescriptor.class), Mockito.eq("snapshot-1")))
Mockito.any(ConnectionDescriptor.class),
Mockito.any(HyperlinkedSerializableRecordingDescriptor.class)))
.thenReturn(future2);
Mockito.when(future2.get()).thenReturn(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ void shouldCreateSnapshot() throws Exception {
CompletableFuture<Boolean> future2 = Mockito.mock(CompletableFuture.class);
Mockito.when(
recordingTargetHelper.verifySnapshot(
Mockito.any(ConnectionDescriptor.class), Mockito.eq("snapshot-1")))
Mockito.any(ConnectionDescriptor.class),
Mockito.any(HyperlinkedSerializableRecordingDescriptor.class)))
.thenReturn(future2);
Mockito.when(future2.get()).thenReturn(true);

Expand Down Expand Up @@ -214,7 +215,8 @@ void shouldHandleSnapshotCreationExceptionDuringVerification() throws Exception
CompletableFuture<Boolean> future2 = Mockito.mock(CompletableFuture.class);
Mockito.when(
recordingTargetHelper.verifySnapshot(
Mockito.any(ConnectionDescriptor.class), Mockito.eq("snapshot-1")))
Mockito.any(ConnectionDescriptor.class),
Mockito.any(HyperlinkedSerializableRecordingDescriptor.class)))
.thenReturn(future2);
Mockito.when(future2.get())
.thenThrow(
Expand Down Expand Up @@ -254,7 +256,8 @@ void shouldHandleFailedSnapshotVerification() throws Exception {
CompletableFuture<Boolean> future2 = Mockito.mock(CompletableFuture.class);
Mockito.when(
recordingTargetHelper.verifySnapshot(
Mockito.any(ConnectionDescriptor.class), Mockito.eq("snapshot-1")))
Mockito.any(ConnectionDescriptor.class),
Mockito.any(HyperlinkedSerializableRecordingDescriptor.class)))
.thenReturn(future2);
Mockito.when(future2.get()).thenReturn(false);

Expand Down
Loading