Skip to content

Commit

Permalink
WIP on feat/scheduler-activity-deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelCourtney committed Dec 12, 2024
1 parent 327fdec commit 698cca6
Show file tree
Hide file tree
Showing 36 changed files with 518 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ create table merlin.activity_directive (
plan_id integer not null,

name text,
source_scheduling_goal_id integer,
source_scheduling_goal_id integer default null,
source_scheduling_goal_invocation_id integer default null,
created_at timestamptz not null default now(),
created_by text,
last_modified_at timestamptz not null default now(),
Expand Down Expand Up @@ -38,6 +39,11 @@ create table merlin.activity_directive (
foreign key (created_by)
references permissions.users
on update cascade
on delete set null,
constraint activity_directive_source_invocation_id_exists
foreign key (source_scheduling_goal_invocation_id)
references merlin.scheduling_specification_goals
on update cascade
on delete set null
);

Expand All @@ -56,6 +62,8 @@ comment on column merlin.activity_directive.name is e''
'The name of this activity_directive.';
comment on column merlin.activity_directive.source_scheduling_goal_id is e''
'The scheduling goal that this activity_directive was generated by.';
comment on column merlin.activity_directive.source_scheduling_goal_invocation_id is e''
'The scheduling goal invocation that this activity_directive was generated by.';
comment on column merlin.activity_directive.created_at is e''
'The time at which this activity_directive was created.';
comment on column merlin.activity_directive.created_by is e''
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Map;

/**
* Creates one activity, and deletes it automatically on subsequent runs.
*/
@SchedulingProcedure
public record ActivityAutoDeletionGoal(boolean deleteAtBeginning) implements Goal {
@NotNull
@Override
public ActivityAutoDelete shouldDeletePastCreations(
@NotNull final Plan plan,
@Nullable final SimulationResults simResults)
{
if (deleteAtBeginning) return new ActivityAutoDelete.AtBeginning(DeletedAnchorStrategy.Error, false);
else return new ActivityAutoDelete.JustBefore(DeletedAnchorStrategy.Error);
}

@Override
public void run(@NotNull final EditablePlan plan) {
plan.create(
"BiteBanana",
new DirectiveStart.Absolute(Duration.MINUTE),
Map.of("biteSize", SerializedValue.of(1))
);

plan.commit();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package gov.nasa.jpl.aerie.e2e.procedural.scheduling;

import gov.nasa.jpl.aerie.e2e.types.GoalInvocationId;
import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

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

public class AutoDeletionTests extends ProceduralSchedulingSetup {
private GoalInvocationId edslId;
private GoalInvocationId procedureId;

@BeforeEach
void localBeforeEach() throws IOException {
try (final var gateway = new GatewayRequests(playwright)) {
final String coexGoalDefinition =
"""
export default function myGoal() {
return Goal.CoexistenceGoal({
forEach: ActivityExpression.ofType(ActivityTypes.BiteBanana),
activityTemplate: ActivityTemplates.GrowBanana({quantity: 1, growingDuration: Temporal.Duration.from({minutes:1})}),
startsAt:TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes : 5}))
})
}""";

edslId = hasura.createSchedulingSpecGoal(
"Coexistence Scheduling Test Goal",
coexGoalDefinition,
"",
specId,
0,
false
);

int procedureJarId = gateway.uploadJarFile("build/libs/ActivityAutoDeletionGoal.jar");
// Add Scheduling Procedure
procedureId = hasura.createSchedulingSpecProcedure(
"Test Scheduling Procedure",
procedureJarId,
specId,
1,
false
);
}
}

@AfterEach
void localAfterEach() throws IOException {
hasura.deleteSchedulingGoal(procedureId.goalId());
hasura.deleteSchedulingGoal(edslId.goalId());
}

@Test
void createsOneActivityIfRunOnce() throws IOException {
final var args = Json
.createObjectBuilder()
.add("deleteAtBeginning", false)
.build();

hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

hasura.awaitScheduling(specId);

final var plan = hasura.getPlan(planId);
final var activities = plan.activityDirectives();

assertEquals(1, activities.size());

assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "BiteBanana")
));
}

@Test
void createsTwoActivitiesSteadyState_JustBefore() throws IOException {
final var args = Json
.createObjectBuilder()
.add("deleteAtBeginning", false)
.build();

hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

hasura.awaitScheduling(specId);

for (int i = 0; i < 3; i++) {
hasura.awaitScheduling(specId);

final var plan = hasura.getPlan(planId);
final var activities = plan.activityDirectives();

assertEquals(2, activities.size());

assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "BiteBanana")
));

assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "GrowBanana")
));
}
}

@Test
void createsOneActivitySteadyState_AtBeginning() throws IOException {
final var args = Json
.createObjectBuilder()
.add("deleteAtBeginning", true)
.build();

hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

hasura.awaitScheduling(specId);

for (int i = 0; i < 3; i++) {
hasura.awaitScheduling(specId);

final var plan = hasura.getPlan(planId);
final var activities = plan.activityDirectives();

assertEquals(1, activities.size());

assertTrue(activities.stream().anyMatch(
it -> Objects.equals(it.type(), "BiteBanana")
));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -710,12 +710,22 @@ public void updatePlanRevisionSchedulingSpec(int planId) throws IOException {
makeRequest(GQL.UPDATE_SCHEDULING_SPECIFICATION_PLAN_REVISION, variables);
}


public GoalInvocationId createSchedulingSpecProcedure(
String name,
int jarId,
int specificationId,
int priority
) throws IOException {
return createSchedulingSpecProcedure(name, jarId, specificationId, priority, true);
}


public GoalInvocationId createSchedulingSpecProcedure(
String name,
int jarId,
int specificationId,
int priority,
boolean simulateAfter
) throws IOException {
final var specGoalBuilder = Json.createObjectBuilder()
.add("goal_metadata",
Expand All @@ -733,7 +743,8 @@ public GoalInvocationId createSchedulingSpecProcedure(
.add("uploaded_jar_id", jarId)
)))))
.add("specification_id", specificationId)
.add("priority", priority);
.add("priority", priority)
.add("simulate_after", simulateAfter);
final var variables = Json.createObjectBuilder().add("spec_goal", specGoalBuilder).build();
final var resp = makeRequest(GQL.CREATE_SCHEDULING_SPEC_GOAL, variables)
.getJsonObject("insert_scheduling_specification_goals_one");
Expand Down Expand Up @@ -768,6 +779,18 @@ public GoalInvocationId createSchedulingSpecGoal(
String description,
int specificationId,
int priority
) throws IOException
{
return createSchedulingSpecGoal(name, definition, description, specificationId, priority, true);
}

public GoalInvocationId createSchedulingSpecGoal(
String name,
String definition,
String description,
int specificationId,
int priority,
boolean simulateAfter
) throws IOException {
final var specGoalBuilder = Json.createObjectBuilder()
.add("goal_metadata",
Expand All @@ -783,6 +806,7 @@ public GoalInvocationId createSchedulingSpecGoal(
.add(Json.createObjectBuilder()
.add("definition", definition))))))
.add("specification_id", specificationId)
.add("simulate_after", simulateAfter)
.add("priority", priority);
final var variables = Json.createObjectBuilder().add("spec_goal", specGoalBuilder).build();
final var resp = makeRequest(GQL.CREATE_SCHEDULING_SPEC_GOAL, variables)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gov.nasa.ammos.aerie.procedural.scheduling

import gov.nasa.ammos.aerie.procedural.scheduling.plan.DeletedAnchorStrategy

sealed interface ActivityAutoDelete {
data class AtBeginning(val anchorStrategy: DeletedAnchorStrategy, val simulateAfter: Boolean): ActivityAutoDelete
data class JustBefore(val anchorStrategy: DeletedAnchorStrategy): ActivityAutoDelete
data object No: ActivityAutoDelete
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
package gov.nasa.ammos.aerie.procedural.scheduling

import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan
import gov.nasa.ammos.aerie.procedural.timeline.plan.Plan
import gov.nasa.ammos.aerie.procedural.timeline.plan.SimulationResults

/** The interface that all scheduling rules must satisfy. */
interface Goal {
/**
* Whether the scheduler should delete this goal's past created activities.
*
* Default implementation returns [ActivityAutoDelete.No]. Override this method
* to specify otherwise and choose a strategy for deleted anchors.
*
* This method may be called multiple times during the scheduling run, and must return the
* same result every time. All calls to this method and [run] during a scheduling run
* will be performed on the same object instance.
*/
fun shouldDeletePastCreations(plan: Plan, simResults: SimulationResults?): ActivityAutoDelete = ActivityAutoDelete.No

/**
* Run the rule.
*
Expand Down
Loading

0 comments on commit 698cca6

Please sign in to comment.