Skip to content

Commit 59365a1

Browse files
committed
Tweaks for test submission flow in MM setup
1 parent a7a4982 commit 59365a1

16 files changed

Lines changed: 1173 additions & 327 deletions

File tree

ECS_RUNNER_LIFETIME.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ This is the normal path for `EXAMPLE` and `PROVISIONAL` scoring during the Submi
4141

4242
### Validation submission scoring
4343

44-
`POST /v6/marathon-match/challenge/:challengeId/test-submission` uploads a validation submission through the configured Submission API and then launches one ECS runner for the requested `configType` (default `PROVISIONAL`). The response includes `submissionId`, `taskArn`, `taskId`, and usually `cloudWatchLogsConsoleUrl`.
44+
`POST /v6/marathon-match/challenge/:challengeId/test-submission` stores the uploaded ZIP as an isolated Marathon Match validation run, then launches one ECS runner for the requested `configType` (default `PROVISIONAL`). The runner downloads the ZIP from Marathon Match API using `VALIDATION_SUBMISSION_DOWNLOAD_URL`, includes `VALIDATION_RUN_ID` in progress/final callbacks, skips Submission API artifact uploads, and `ScoringResultService` stores progress/final score details on the validation run instead of creating Review API summations. The response includes `testSubmissionId`, `submissionId` (same validation-run id for runner compatibility), `status`, `taskArn`, `taskId`, and usually `cloudWatchLogsConsoleUrl`.
4545

46-
We do this to support admins / copilots having the ability to test the scorer before the challenge launches.
46+
Use `GET /v6/marathon-match/challenge/:challengeId/test-submission/:testSubmissionId` to poll for completion and display the score/metadata. We do this to support admins / copilots having the ability to test the scorer before the challenge launches without adding a real submission or review to the challenge.
4747

4848
### Manual latest-submission rerun
4949

ecs-runner/boilerplate/src/main/java/com/topcoder/scorer/services/SubmissionService.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,21 @@ public void updateSubmissionStatus(String submissionId, String status) throws Ex
8585
*/
8686
public void downloadSubmission(String submissionId, String targetDir) throws Exception {
8787
String url = submissionApiUrl + "/submissions/" + submissionId + "/download";
88+
downloadSubmissionFromUrl(url, targetDir, submissionId);
89+
}
90+
91+
/**
92+
* Downloads a submission zip from an explicit URL and extracts it to the target directory.
93+
* @param url Absolute URL returning the submission ZIP.
94+
* @param targetDir Directory to extract the submission to.
95+
* @param submissionId Submission or validation-run ID for contextual logs.
96+
* @throws Exception if download or extraction fails.
97+
*/
98+
public void downloadSubmissionFromUrl(
99+
String url,
100+
String targetDir,
101+
String submissionId
102+
) throws Exception {
88103
HttpGet get = new HttpGet(url);
89104
get.setHeader("Authorization", "Bearer " + accessToken);
90105
logInfo(submissionId, "GET " + url + " (submission zip download)");

ecs-runner/src/main/java/com/topcoder/runner/EcsRunnerMain.java

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ public static void main(String[] args) {
194194
String reviewTypeId = null;
195195
String scorecardId = null;
196196
String submissionApiUrl = null;
197+
String validationRunId = null;
198+
String validationSubmissionDownloadUrl = null;
197199

198200
try {
199201
challengeId = getRequiredEnv("TESTER_CONFIG_ID");
@@ -209,9 +211,24 @@ public static void main(String[] args) {
209211
if (reviewId != null && reviewId.isEmpty()) {
210212
reviewId = null;
211213
}
214+
validationRunId = getOptionalEnv("VALIDATION_RUN_ID", "");
215+
if (validationRunId != null && validationRunId.isEmpty()) {
216+
validationRunId = null;
217+
}
218+
validationSubmissionDownloadUrl = getOptionalEnv(
219+
"VALIDATION_SUBMISSION_DOWNLOAD_URL",
220+
""
221+
);
222+
if (
223+
validationSubmissionDownloadUrl != null
224+
&& validationSubmissionDownloadUrl.isEmpty()
225+
) {
226+
validationSubmissionDownloadUrl = null;
227+
}
212228
testPhase = normalizeTestPhase(getOptionalEnv("TEST_PHASE", "provisional"));
213229
long phaseStartSeed = getOptionalLongEnv("PHASE_START_SEED", 0L);
214230
int phaseNumberOfTests = getOptionalIntEnv("PHASE_NUMBER_OF_TESTS", 0);
231+
boolean validationMode = !isBlank(validationRunId);
215232
setLogContext(challengeId, submissionId, testPhase);
216233

217234
logInfo(
@@ -230,6 +247,8 @@ public static void main(String[] args) {
230247
+ phaseStartSeed
231248
+ ", phaseNumberOfTests="
232249
+ phaseNumberOfTests
250+
+ ", validationRunId="
251+
+ (validationMode ? validationRunId : "<none>")
233252
);
234253
requireTrustedRunnerProcess();
235254

@@ -318,9 +337,24 @@ public static void main(String[] args) {
318337
+ " to "
319338
+ submissionDir
320339
+ " via "
321-
+ config.getSubmissionApiUrl()
340+
+ (validationMode
341+
? validationSubmissionDownloadUrl
342+
: config.getSubmissionApiUrl())
322343
);
323-
submissionService.downloadSubmission(submissionId, submissionDir.toString());
344+
if (validationMode) {
345+
if (isBlank(validationSubmissionDownloadUrl)) {
346+
throw new RuntimeException(
347+
"VALIDATION_SUBMISSION_DOWNLOAD_URL is required when VALIDATION_RUN_ID is set."
348+
);
349+
}
350+
submissionService.downloadSubmissionFromUrl(
351+
validationSubmissionDownloadUrl,
352+
submissionDir.toString(),
353+
submissionId
354+
);
355+
} else {
356+
submissionService.downloadSubmission(submissionId, submissionDir.toString());
357+
}
324358
logInfo("api.download-submission", "Submission download and extraction complete");
325359
logDirectorySnapshot(submissionDir, 100);
326360

@@ -345,6 +379,7 @@ public static void main(String[] args) {
345379
testPhase,
346380
reviewTypeId,
347381
reviewId,
382+
validationRunId,
348383
scorecardId,
349384
0.0,
350385
TEST_STATUS_IN_PROGRESS,
@@ -380,6 +415,7 @@ public static void main(String[] args) {
380415
accessTokenProvider,
381416
reviewTypeId,
382417
reviewId,
418+
validationRunId,
383419
scorecardId
384420
);
385421
} finally {
@@ -427,16 +463,24 @@ public static void main(String[] args) {
427463
callbackMetadata
428464
);
429465

430-
logInfo("artifacts.upload", "Uploading submission artifacts");
431-
uploadArtifacts(
432-
httpClient,
433-
config.getSubmissionApiUrl(),
434-
accessTokenProvider,
435-
submissionId,
436-
testPhase,
437-
submissionDir
438-
);
439-
logInfo("artifacts.upload", "Artifact upload completed");
466+
if (validationMode) {
467+
logInfo(
468+
"artifacts.upload",
469+
"Skipping submission artifact upload for validation run "
470+
+ validationRunId
471+
);
472+
} else {
473+
logInfo("artifacts.upload", "Uploading submission artifacts");
474+
uploadArtifacts(
475+
httpClient,
476+
config.getSubmissionApiUrl(),
477+
accessTokenProvider,
478+
submissionId,
479+
testPhase,
480+
submissionDir
481+
);
482+
logInfo("artifacts.upload", "Artifact upload completed");
483+
}
440484

441485
ScoringCallbackRequest callbackRequest = new ScoringCallbackRequest(
442486
challengeId,
@@ -445,6 +489,7 @@ public static void main(String[] args) {
445489
testPhase,
446490
reviewTypeId,
447491
reviewId,
492+
validationRunId,
448493
scorerConfig.getScoreCardId(),
449494
callbackMetadata,
450495
callbackCurrentReview,
@@ -486,18 +531,27 @@ public static void main(String[] args) {
486531
error
487532
);
488533
writeFailureArtifactLog(submissionDir, submissionId, error);
489-
uploadFailureArtifactsSafely(
490-
submissionApiUrl,
491-
accessTokenProvider,
492-
submissionId,
493-
testPhase,
494-
submissionDir
495-
);
534+
if (isBlank(validationRunId)) {
535+
uploadFailureArtifactsSafely(
536+
submissionApiUrl,
537+
accessTokenProvider,
538+
submissionId,
539+
testPhase,
540+
submissionDir
541+
);
542+
} else {
543+
logInfo(
544+
"artifacts.upload",
545+
"Skipping failure artifact upload for validation run "
546+
+ validationRunId
547+
);
548+
}
496549
postFailureProgressSafely(
497550
challengeId,
498551
submissionId,
499552
testPhase,
500553
reviewId,
554+
validationRunId,
501555
accessTokenProvider,
502556
marathonMatchBaseUrl,
503557
reviewTypeId,
@@ -646,6 +700,7 @@ private static TesterExecutionResult runTesterInIsolation(
646700
AccessTokenProvider accessTokenProvider,
647701
String reviewTypeId,
648702
String reviewId,
703+
String validationRunId,
649704
String scorecardId
650705
) throws Exception {
651706
Path scorerConfigPath = null;
@@ -712,6 +767,7 @@ private static TesterExecutionResult runTesterInIsolation(
712767
testPhase,
713768
reviewTypeId,
714769
reviewId,
770+
validationRunId,
715771
scorecardId,
716772
progressUpdate.getProgress(),
717773
progressUpdate.getStatus(),
@@ -2520,6 +2576,7 @@ private static void postScoringProgressSafely(
25202576
* @param submissionId Submission ID.
25212577
* @param testPhase Scoring phase.
25222578
* @param reviewId Optional review ID for system scoring.
2579+
* @param validationRunId Optional isolated validation run ID for Score Operations tests.
25232580
* @param accessTokenProvider Refresh-capable bearer token provider.
25242581
* @param marathonMatchBaseUrl Marathon Match API base URL, when available.
25252582
* @param reviewTypeId Review type ID, when available.
@@ -2531,6 +2588,7 @@ private static void postFailureProgressSafely(
25312588
String submissionId,
25322589
String testPhase,
25332590
String reviewId,
2591+
String validationRunId,
25342592
AccessTokenProvider accessTokenProvider,
25352593
String marathonMatchBaseUrl,
25362594
String reviewTypeId,
@@ -2561,6 +2619,7 @@ private static void postFailureProgressSafely(
25612619
testPhase,
25622620
reviewTypeId,
25632621
reviewId,
2622+
validationRunId,
25642623
scorecardId,
25652624
lastReportedProgress,
25662625
TEST_STATUS_FAILED,
@@ -6166,6 +6225,9 @@ private static class ScoringCallbackRequest {
61666225
@JsonProperty("reviewId")
61676226
private final String reviewId;
61686227

6228+
@JsonProperty("validationRunId")
6229+
private final String validationRunId;
6230+
61696231
@JsonProperty("scorecardId")
61706232
private final String scorecardId;
61716233

@@ -6185,6 +6247,7 @@ private static class ScoringCallbackRequest {
61856247
String testPhase,
61866248
String reviewTypeId,
61876249
String reviewId,
6250+
String validationRunId,
61886251
String scorecardId,
61896252
Map<String, Object> metadata,
61906253
Map<String, Object> currentReview,
@@ -6196,6 +6259,7 @@ private static class ScoringCallbackRequest {
61966259
this.testPhase = testPhase;
61976260
this.reviewTypeId = reviewTypeId;
61986261
this.reviewId = reviewId;
6262+
this.validationRunId = validationRunId;
61996263
this.scorecardId = scorecardId;
62006264
this.metadata = metadata;
62016265
this.currentReview = currentReview;
@@ -6222,6 +6286,9 @@ private static class ScoringProgressRequest {
62226286
@JsonProperty("reviewId")
62236287
private final String reviewId;
62246288

6289+
@JsonProperty("validationRunId")
6290+
private final String validationRunId;
6291+
62256292
@JsonProperty("scorecardId")
62266293
private final String scorecardId;
62276294

@@ -6252,6 +6319,7 @@ private static class ScoringProgressRequest {
62526319
String testPhase,
62536320
String reviewTypeId,
62546321
String reviewId,
6322+
String validationRunId,
62556323
String scorecardId,
62566324
double progress,
62576325
String status,
@@ -6266,6 +6334,7 @@ private static class ScoringProgressRequest {
62666334
this.testPhase = testPhase;
62676335
this.reviewTypeId = reviewTypeId;
62686336
this.reviewId = reviewId;
6337+
this.validationRunId = validationRunId;
62696338
this.scorecardId = scorecardId;
62706339
this.progress = Math.min(1.0, Math.max(0.0, progress));
62716340
this.status = status;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
CREATE TABLE "marathon_match"."testSubmissionRun" (
2+
"id" VARCHAR(14) NOT NULL DEFAULT nanoid(),
3+
"challengeId" VARCHAR(36) NOT NULL,
4+
"configType" "marathon_match"."PhaseConfigType" NOT NULL,
5+
"memberId" TEXT,
6+
"fileName" TEXT NOT NULL,
7+
"mimeType" TEXT,
8+
"fileSize" INTEGER NOT NULL,
9+
"fileContent" BYTEA NOT NULL,
10+
"status" TEXT NOT NULL DEFAULT 'QUEUED',
11+
"score" DOUBLE PRECISION,
12+
"message" TEXT,
13+
"metadata" JSONB,
14+
"currentReview" JSONB,
15+
"impactedReviews" JSONB,
16+
"progress" DOUBLE PRECISION,
17+
"completedTests" INTEGER,
18+
"totalTests" INTEGER,
19+
"failedTests" INTEGER,
20+
"taskArn" TEXT,
21+
"taskId" TEXT,
22+
"cloudWatchLogsConsoleUrl" TEXT,
23+
"completedAt" TIMESTAMP(3),
24+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
25+
"updatedAt" TIMESTAMP(3) NOT NULL,
26+
"createdBy" TEXT,
27+
28+
CONSTRAINT "testSubmissionRun_pkey" PRIMARY KEY ("id")
29+
);
30+
31+
CREATE INDEX "testSubmissionRun_challengeId_idx" ON "marathon_match"."testSubmissionRun"("challengeId");
32+
CREATE INDEX "testSubmissionRun_status_idx" ON "marathon_match"."testSubmissionRun"("status");
33+
CREATE INDEX "testSubmissionRun_createdAt_idx" ON "marathon_match"."testSubmissionRun"("createdAt");

prisma/schema.prisma

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,39 @@ model submissionRunnerLog {
121121
@@schema("marathon_match")
122122
}
123123

124+
model testSubmissionRun {
125+
id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14)
126+
challengeId String @db.VarChar(36)
127+
configType PhaseConfigType
128+
memberId String?
129+
fileName String
130+
mimeType String?
131+
fileSize Int
132+
fileContent Bytes
133+
status String @default("QUEUED")
134+
score Float?
135+
message String? @db.Text
136+
metadata Json?
137+
currentReview Json?
138+
impactedReviews Json?
139+
progress Float?
140+
completedTests Int?
141+
totalTests Int?
142+
failedTests Int?
143+
taskArn String?
144+
taskId String?
145+
cloudWatchLogsConsoleUrl String?
146+
completedAt DateTime?
147+
createdAt DateTime @default(now())
148+
updatedAt DateTime @updatedAt
149+
createdBy String?
150+
151+
@@index([challengeId])
152+
@@index([status])
153+
@@index([createdAt])
154+
@@schema("marathon_match")
155+
}
156+
124157
model scoringCompletionEmailNotification {
125158
id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14)
126159
challengeId String @db.VarChar(36)

0 commit comments

Comments
 (0)