Skip to content

Commit c5400a3

Browse files
authored
Merge pull request #1354 from NASA-AMMOS/fix/directive-validations-with-null-model-id
Serialize `NoSuchMissionModel` exception out in activity validations
2 parents b683cfe + 126a2e6 commit c5400a3

File tree

9 files changed

+73
-8
lines changed

9 files changed

+73
-8
lines changed

e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/AutomaticValidationTests.java

+25
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.junit.jupiter.api.TestInstance;
1313

1414
import javax.json.Json;
15+
import javax.json.JsonValue;
1516
import java.io.IOException;
1617
import java.util.List;
1718

@@ -130,6 +131,30 @@ void instantiationError() throws IOException, InterruptedException {
130131
), activityValidation);
131132
}
132133

134+
@Test
135+
void noSuchMissionModelError() throws IOException, InterruptedException {
136+
final var activityId = hasura.insertActivity(
137+
planId,
138+
"BiteBanana",
139+
"1h",
140+
JsonValue.EMPTY_JSON_OBJECT
141+
);
142+
Thread.sleep(1000); // TODO consider a while loop here
143+
144+
hasura.deleteMissionModel(modelId);
145+
146+
final var arguments = Json.createObjectBuilder().add("biteSize", 2).build();
147+
hasura.updateActivityDirectiveArguments(planId, activityId, arguments);
148+
Thread.sleep(1000); // TODO consider a while loop here
149+
150+
final var activityValidations = hasura.getActivityValidations(planId);
151+
final ActivityValidation activityValidation = activityValidations.get((long) activityId);
152+
assertEquals(
153+
new ActivityValidation.NoSuchMissionModelFailure("no such mission model", "0"),
154+
activityValidation
155+
);
156+
}
157+
133158
@Test
134159
void exceptionDuringValidationHandled() throws IOException, InterruptedException {
135160
final var exceptionActivityId = hasura.insertActivity(

e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/types/ActivityValidation.java

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ record Success() implements ActivityValidation {}
1111
record InstantiationFailure(List<String> extraneousArguments, List<String> missingArguments, List<?> unconstructableArguments) implements ActivityValidation {}
1212
record ValidationFailure(List<ValidationNotice> notices) implements ActivityValidation {}
1313
record NoSuchActivityTypeFailure(String message, String activityType) implements ActivityValidation {}
14+
record NoSuchMissionModelFailure(String message, String modelId) implements ActivityValidation {}
1415

1516
record ValidationNotice(List<String> subjects, String message) { }
1617
record UnconstructableArgument(String name, String failure) { }
@@ -44,6 +45,10 @@ static ActivityValidation fromJSON(JsonObject obj) {
4445
getStringArray($, "subjects"),
4546
$.asJsonObject().getString("message"))));
4647
case "NO_SUCH_ACTIVITY_TYPE" -> new NoSuchActivityTypeFailure(errors.getJsonObject("noSuchActivityError").getString("message"), errors.getJsonObject("noSuchActivityError").getString("activity_type"));
48+
case "NO_SUCH_MISSION_MODEL" -> new NoSuchMissionModelFailure(
49+
errors.getJsonObject("noSuchMissionModelError").getString("message"),
50+
errors.getJsonObject("noSuchMissionModelError").getString("mission_model_id")
51+
);
4752
default -> throw new RuntimeException("Unhandled error type: " + type);
4853
};
4954
}

e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/GQL.java

+9
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,15 @@ query Simulate($plan_id: Int!) {
501501
simulationDatasetId
502502
}
503503
}"""),
504+
UPDATE_ACTIVITY_DIRECTIVE_ARGUMENTS("""
505+
mutation updateActivityDirectiveArguments($id: Int!, $plan_id: Int!, $arguments: jsonb!) {
506+
updateActivityDirectiveArguments: update_activity_directive_by_pk(
507+
pk_columns: {id: $id, plan_id: $plan_id},
508+
_set: {arguments: $arguments}
509+
) {
510+
id
511+
}
512+
}"""),
504513
UPDATE_CONSTRAINT("""
505514
mutation updateConstraint($constraintId: Int!, $constraintDefinition: String!) {
506515
constraint: insert_constraint_definition_one(object: {constraint_id: $constraintId, definition: $constraintDefinition}) {

e2e-tests/src/test/java/gov/nasa/jpl/aerie/e2e/utils/HasuraRequests.java

+10
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,16 @@ public int insertActivity(int planId, String type, String startOffset, JsonObjec
192192
return makeRequest(GQL.CREATE_ACTIVITY_DIRECTIVE, variables).getJsonObject("createActivityDirective").getInt("id");
193193
}
194194

195+
public void updateActivityDirectiveArguments(int planId, int activityId, JsonObject arguments) throws IOException {
196+
final var variables = Json.createObjectBuilder()
197+
.add("plan_id", planId)
198+
.add("id", activityId)
199+
.add("arguments", arguments)
200+
.build();
201+
202+
makeRequest(GQL.UPDATE_ACTIVITY_DIRECTIVE_ARGUMENTS, variables);
203+
}
204+
195205
public void deleteActivity(int planId, int activityId) throws IOException {
196206
final var variables = Json.createObjectBuilder()
197207
.add("plan_id", planId)

merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/http/ResponseSerializers.java

+8
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,14 @@ public static JsonValue serializeBulkArgumentValidationResponse(BulkArgumentVali
191191
return Json.createObjectBuilder(serializeInstantiationException(f.ex()).asJsonObject())
192192
.add("type", "INSTANTIATION_ERRORS")
193193
.build();
194+
} else if (response instanceof BulkArgumentValidationResponse.NoSuchMissionModelError m) {
195+
return Json.createObjectBuilder()
196+
.add("success", JsonValue.FALSE)
197+
.add("type", "NO_SUCH_MISSION_MODEL")
198+
.add("errors", Json.createObjectBuilder()
199+
.add("noSuchMissionModelError", serializeNoSuchMissionModelException(m.ex()))
200+
.build())
201+
.build();
194202
}
195203

196204
// This should never happen, but we don't have exhaustive pattern matching

merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/remotes/postgres/GetUnvalidatedDirectivesAction.java

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import gov.nasa.jpl.aerie.merlin.server.models.ActivityDirectiveId;
66
import gov.nasa.jpl.aerie.merlin.server.models.MissionModelId;
77
import gov.nasa.jpl.aerie.merlin.server.models.PlanId;
8-
import gov.nasa.jpl.aerie.merlin.server.models.Timestamp;
98

109
import java.sql.Connection;
1110
import java.sql.PreparedStatement;

merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/LocalMissionModelService.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,22 @@ public List<ValidationNotice> validateActivityArguments(final String missionMode
133133

134134
public List<BulkArgumentValidationResponse> validateActivityArgumentsBulk(
135135
final MissionModelId modelId,
136-
final List<ActivityDirectiveForValidation> activities
137-
) throws NoSuchMissionModelException, MissionModelLoadException {
136+
final List<ActivityDirectiveForValidation> activities) {
138137
// load mission model once for all activities
139-
final var modelType = this.loadMissionModelType(modelId.toString());
138+
ModelType<?, ?> modelType;
139+
try {
140+
modelType = this.loadMissionModelType(modelId.toString());
141+
// try and catch NoSuchMissionModel here, so we can serialize it out to each activity validation
142+
// rather than catching it at a higher level in the workerLoop itself
143+
} catch (NoSuchMissionModelException e) {
144+
return activities.stream()
145+
.map(directive -> new BulkArgumentValidationResponse.NoSuchMissionModelError(e))
146+
.collect(Collectors.toList());
147+
} catch (MissionModelLoadException e) {
148+
log.error("Caught MissionModelLoadException, skipping this batch but leaving validations pending...");
149+
log.error(e.toString());
150+
return List.of();
151+
}
140152
final var registry = DirectiveTypeRegistry.extract(modelType);
141153

142154
// map all directives to validation response

merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/MissionModelService.java

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ record InstantiationFailure(InstantiationException ex) implements BulkEffective
105105
sealed interface BulkArgumentValidationResponse {
106106
record Success() implements BulkArgumentValidationResponse { }
107107
record Validation(List<ValidationNotice> notices) implements BulkArgumentValidationResponse { }
108+
record NoSuchMissionModelError(NoSuchMissionModelException ex) implements BulkArgumentValidationResponse { }
108109
record NoSuchActivityError(NoSuchActivityTypeException ex) implements BulkArgumentValidationResponse { }
109110
record InstantiationError(InstantiationException ex) implements BulkArgumentValidationResponse { }
110111
}

merlin-server/src/main/java/gov/nasa/jpl/aerie/merlin/server/services/ValidationWorker.java

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package gov.nasa.jpl.aerie.merlin.server.services;
22

33
import gov.nasa.jpl.aerie.merlin.server.models.ActivityDirectiveForValidation;
4-
import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService.NoSuchMissionModelException;
54
import gov.nasa.jpl.aerie.merlin.server.services.MissionModelService.BulkArgumentValidationResponse;
65
import org.apache.commons.lang3.tuple.Pair;
76
import org.slf4j.Logger;
@@ -49,9 +48,6 @@ public void workerLoop() {
4948
final var duration = (endTime - beginTime) / 1_000_000.0;
5049
logger.debug("processed model batch of size {} in {} ms", unvalidatedDirectives.size(), duration);
5150
}
52-
53-
} catch (NoSuchMissionModelException ex) {
54-
logger.error("Validation request failed due to no such mission model: {}", ex.toString());
5551
} catch (InterruptedException ex) {
5652
// we were interrupted, so exit gracefully
5753
return;

0 commit comments

Comments
 (0)