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

Procedural Scheduling activity auto-deletion #1618

Draft
wants to merge 10 commits into
base: develop
Choose a base branch
from
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,47 @@
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;

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.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 java.util.Map;
import java.util.Optional;

/**
* Creates three activities in a chain of anchors, then deletes one.
*/
@SchedulingProcedure
public record ActivityDeletionGoal(int whichToDelete, DeletedAnchorStrategy anchorStrategy) implements Goal {
@Override
public void run(@NotNull final EditablePlan plan) {
final var ids = new ActivityDirectiveId[3];

ids[0] = plan.create(
"BiteBanana",
new DirectiveStart.Absolute(Duration.HOUR),
Map.of("biteSize", SerializedValue.of(0))
);
ids[1] = plan.create(
"BiteBanana",
new DirectiveStart.Anchor(ids[0], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.End),
Map.of("biteSize", SerializedValue.of(1))
);
ids[2] = plan.create(
"BiteBanana",
new DirectiveStart.Anchor(ids[1], Duration.HOUR, DirectiveStart.Anchor.AnchorPoint.Start),
Map.of("biteSize", SerializedValue.of(2))
);

if (whichToDelete >= 0) {
plan.delete(ids[whichToDelete], anchorStrategy);
}

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 @@ -134,6 +134,14 @@ void executeEDSLAndProcedure() throws IOException {
final var args = Json.createObjectBuilder().add("quantity", 4).build();
hasura.updateSchedulingSpecGoalArguments(procedureId.invocationId(), args);

final String recurrenceGoalDefinition =
"""
export default function myGoal() {
return Goal.ActivityRecurrenceGoal({
activityTemplate: ActivityTemplates.PeelBanana({peelDirection: 'fromStem'}),
interval: Temporal.Duration.from({hours:1})
})}""";

hasura.createSchedulingSpecGoal(
"Recurrence Scheduling Test Goal",
recurrenceGoalDefinition,
Expand Down
Loading
Loading