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 13, 2024
1 parent 327fdec commit 1a3d737
Show file tree
Hide file tree
Showing 38 changed files with 536 additions and 109 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
alter table merlin.activity_directive
drop constraint activity_directive_source_invocation_id_exists,

drop column source_scheduling_goal_invocation_id;

call migrations.mark_migration_rolled_back('12');
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
alter table merlin.activity_directive
add column source_scheduling_goal_invocation_id integer default null,

add constraint activity_directive_source_invocation_id_exists
foreign key (source_scheduling_goal_invocation_id)
references scheduler.scheduling_specification_goals
on update cascade
on delete set null;

comment on column merlin.activity_directive.source_scheduling_goal_invocation_id is e''
'The scheduling goal invocation that this activity_directive was generated by.';

call migrations.mark_migration_applied('12');
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ create table merlin.activity_directive (

name text,
source_scheduling_goal_id integer,
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 scheduler.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 1a3d737

Please sign in to comment.