Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,53 @@
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;

import gov.nasa.ammos.aerie.procedural.scheduling.ActivityAutoDelete;
import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy;
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan;
import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
import gov.nasa.jpl.aerie.types.ActivityDirectiveId;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Map;

@SchedulingProcedure
public record AnchorCascadeDeleteGoal() implements Goal {

@Override
public ActivityAutoDelete shouldDeletePastCreations(
@NotNull final Plan plan,
@Nullable final SimulationResults simResults) {
// Delete activities created by previous runs of this goal, using Cascade to
// handle anchored activities
return new ActivityAutoDelete.AtBeginning(DeletedAnchorStrategy.Cascade, false);
}

@Override
public void run(@NotNull final EditablePlan plan) {
final var ids = new ActivityDirectiveId[3];

ids[0] = plan.create(
"BiteBanana",

Check failure on line 36 in e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/AnchorCascadeDeleteGoal.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "BiteBanana" 3 times.

See more on https://sonarcloud.io/project/issues?id=NASA-AMMOS_aerie&issues=AZ8aeDHA54hngM_hGPKx&open=AZ8aeDHA54hngM_hGPKx&pullRequest=1839
new DirectiveStart.Absolute(Duration.HOUR),
Map.of("biteSize", SerializedValue.of(1))

Check failure on line 38 in e2e-tests/src/main/java/gov/nasa/jpl/aerie/e2e/procedural/scheduling/procedures/AnchorCascadeDeleteGoal.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "biteSize" 3 times.

See more on https://sonarcloud.io/project/issues?id=NASA-AMMOS_aerie&issues=AZ8aeDHA54hngM_hGPKw&open=AZ8aeDHA54hngM_hGPKw&pullRequest=1839
);
ids[1] = plan.create(
"BiteBanana",
new DirectiveStart.Anchor(ids[0], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.End),
Map.of("biteSize", SerializedValue.of(2))
);
ids[2] = plan.create(
"BiteBanana",
new DirectiveStart.Anchor(ids[1], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.Start),
Map.of("biteSize", SerializedValue.of(3))
);

plan.commit();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@

import javax.json.Json;
import java.io.IOException;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class AutoDeletionTests extends ProceduralTestingSetup {
private GoalInvocationId edslId;
private GoalInvocationId procedureId;
private GoalInvocationId cascadeProcedureId;

@BeforeEach
void localBeforeEach() throws IOException {
Expand Down Expand Up @@ -51,7 +54,7 @@ the beginning of the run (before goal 1) or just before (in between 1 and 2).
false
);

int procedureJarId = gateway.uploadJarFile("build/libs/ActivityAutoDeletionGoal.jar");
final int procedureJarId = gateway.uploadJarFile("build/libs/ActivityAutoDeletionGoal.jar");
// Add Scheduling Procedure
procedureId = hasura.createSchedulingSpecProcedure(
"Test Scheduling Procedure",
Expand All @@ -60,11 +63,23 @@ the beginning of the run (before goal 1) or just before (in between 1 and 2).
1,
false
);

// Upload and by default disable the cascade test procedure
final int cascadeJarProcedureId = gateway.uploadJarFile("build/libs/AnchorCascadeDeleteGoal.jar");
cascadeProcedureId = hasura.createSchedulingSpecProcedure(
"Anchor Deletion Cascade Mode Test Procedure",
cascadeJarProcedureId,
specId,
2,
true
);
hasura.updateSchedulingSpecEnabled(cascadeProcedureId.invocationId(), false);
}
}

@AfterEach
void localAfterEach() throws IOException {
hasura.deleteSchedulingGoal(cascadeProcedureId.goalId());
hasura.deleteSchedulingGoal(procedureId.goalId());
hasura.deleteSchedulingGoal(edslId.goalId());
}
Expand Down Expand Up @@ -186,4 +201,52 @@ void createsOneActivitySteadyState_AtBeginning() throws IOException {
));
}
}

@Test
void schedulerDeletesCascade() throws IOException {
// Enable the cascade test, disable the other two
hasura.updateSchedulingSpecEnabled(cascadeProcedureId.invocationId(), true);
hasura.updateSchedulingSpecEnabled(procedureId.invocationId(), false);
hasura.updateSchedulingSpecEnabled(edslId.invocationId(), false);

// Run Scheduling
hasura.awaitScheduling(specId);

// Get a record of the plan post-scheduling
final var originalActivities = hasura.getPlan(planId).activityDirectives();

// Run Scheduling again. This will trigger the goal's autodeletion behavior
hasura.updatePlanRevisionSchedulingSpec(planId);
hasura.awaitScheduling(specId);

// Get a new copy of the plan
final var updatedActivities = hasura.getPlan(planId).activityDirectives();

// Sort the activities on their bite size
originalActivities.sort(Comparator.comparingInt(a -> a.arguments().getInt("biteSize")));
updatedActivities.sort(Comparator.comparingInt(a -> a.arguments().getInt("biteSize")));

// Prove that the activities were deleted and remade during scheduling
assertEquals(originalActivities.size(), updatedActivities.size());
for(int i = 0; i < originalActivities.size(); ++i) {
final var originalAct = originalActivities.get(i);
final var updatedAct = updatedActivities.get(i);

// The Activities should be identical, except for their ids, which should differ
assertNotEquals(originalAct.id(), updatedAct.id());

if(originalAct.anchorId() == null) {
assertNull(updatedAct.anchorId());
} else {
assertNotEquals(originalAct.anchorId(), updatedAct.anchorId());
}

assertEquals(originalAct.planId(), updatedAct.planId());
assertEquals(originalAct.type(), updatedAct.type());
assertEquals(originalAct.startOffset(), updatedAct.startOffset());
assertEquals(originalAct.arguments(), updatedAct.arguments());
assertEquals(originalAct.name(), updatedAct.name());
assertEquals(originalAct.anchoredToStart(), updatedAct.anchoredToStart());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ interface EditablePlan: Plan {
*/
fun delete(directive: Directive<AnyDirective>, strategy: DeletedAnchorStrategy)

/**
* Delete an activity if it exists with a strategy to handle activities that are anchored to it.
*
* If other anchored activities are affected, extra addition and deletion edits may be created.
*/
fun deleteIfExists(id: ActivityDirectiveId, strategy: DeletedAnchorStrategy)

/** Commit plan edits, making them final. */
fun commit()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,14 @@ class DefaultEditablePlanDriver(
delete(matchingDirectives.first(), strategy)
}

override fun deleteIfExists(id: ActivityDirectiveId, strategy: DeletedAnchorStrategy) {
val matchingDirectives = directives().filter { it.id == id }.collect()
if (matchingDirectives.isEmpty()) return;
if (matchingDirectives.size > 1) throw Exception("multiple activities with ID found: $id")

delete(matchingDirectives.first(), strategy)
}

override fun commit() {
// Early return if there are no changes. This prevents multiple commits from sharing ownership of the set,
// because new sets are only created when edits are made.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,12 @@ private void deletePastCreations(
final DeletedAnchorStrategy strategy,
final Map<ActivityDirectiveId, GoalId> sourceSchedulingGoals
) {
for (final var activity: ((SchedulerPlanEditAdapter) plan.getAdapter()).getPlan().getActivities()) {
final var goalId = sourceSchedulingGoals.getOrDefault(activity.id(), null);
if (goalId != null && goalId.goalInvocationId().equals(this.goalId.goalInvocationId())) {
plan.delete(activity.id(), strategy);
}
}
// Get the filtered list of activity ids to delete
sourceSchedulingGoals
.entrySet()
.stream()
.filter(entry -> entry.getValue() != null && entry.getValue().goalInvocationId().equals(this.goalId.goalInvocationId()))
.map(Map.Entry::getKey)
.forEach(id -> plan.deleteIfExists(id, strategy));
}
}
Loading