|
2 | 2 |
|
3 | 3 | import com.codahale.metrics.annotation.Timed; |
4 | 4 | import com.comet.opik.api.error.ErrorMessage; |
5 | | -import com.comet.opik.api.runner.BridgeCommand; |
6 | | -import com.comet.opik.api.runner.BridgeCommandBatchResponse; |
7 | | -import com.comet.opik.api.runner.BridgeCommandNextRequest; |
8 | | -import com.comet.opik.api.runner.BridgeCommandResultRequest; |
9 | | -import com.comet.opik.api.runner.BridgeCommandSubmitRequest; |
10 | | -import com.comet.opik.api.runner.BridgeCommandSubmitResponse; |
11 | 5 | import com.comet.opik.api.runner.CreateLocalRunnerJobRequest; |
12 | 6 | import com.comet.opik.api.runner.LocalRunner; |
13 | 7 | import com.comet.opik.api.runner.LocalRunnerConnectRequest; |
14 | 8 | import com.comet.opik.api.runner.LocalRunnerConnectResponse; |
15 | | -import com.comet.opik.api.runner.LocalRunnerHeartbeatRequest; |
16 | 9 | import com.comet.opik.api.runner.LocalRunnerHeartbeatResponse; |
17 | 10 | import com.comet.opik.api.runner.LocalRunnerJob; |
18 | 11 | import com.comet.opik.api.runner.LocalRunnerJobResultRequest; |
|
24 | 17 | import com.comet.opik.infrastructure.LocalRunnerConfig; |
25 | 18 | import com.comet.opik.infrastructure.auth.RequestContext; |
26 | 19 | import com.comet.opik.infrastructure.ratelimit.RateLimited; |
27 | | -import com.fasterxml.jackson.databind.JsonNode; |
28 | 20 | import com.fasterxml.jackson.databind.node.NullNode; |
29 | 21 | import io.swagger.v3.oas.annotations.Operation; |
30 | 22 | import io.swagger.v3.oas.annotations.headers.Header; |
|
42 | 34 | import jakarta.ws.rs.Consumes; |
43 | 35 | import jakarta.ws.rs.DefaultValue; |
44 | 36 | import jakarta.ws.rs.GET; |
45 | | -import jakarta.ws.rs.PATCH; |
46 | 37 | import jakarta.ws.rs.POST; |
47 | 38 | import jakarta.ws.rs.PUT; |
48 | 39 | import jakarta.ws.rs.Path; |
@@ -164,35 +155,18 @@ public Response registerAgents(@PathParam("runnerId") UUID runnerId, |
164 | 155 | return Response.noContent().build(); |
165 | 156 | } |
166 | 157 |
|
167 | | - @PATCH |
168 | | - @Path("/{runnerId}/checklist") |
169 | | - @RateLimited |
170 | | - @Operation(operationId = "patchChecklist", summary = "Patch runner checklist", description = "Partial update of the runner's checklist (deep merge)", responses = { |
171 | | - @ApiResponse(responseCode = "204", description = "No content"), |
172 | | - @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorMessage.class)))}) |
173 | | - public Response patchChecklist(@PathParam("runnerId") UUID runnerId, |
174 | | - @RequestBody(content = @Content(schema = @Schema(implementation = Object.class))) @NotNull JsonNode updates) { |
175 | | - ensureEnabled(); |
176 | | - String workspaceId = requestContext.get().getWorkspaceId(); |
177 | | - String userName = requestContext.get().getUserName(); |
178 | | - runnerService.patchChecklist(runnerId, workspaceId, userName, updates); |
179 | | - return Response.noContent().build(); |
180 | | - } |
181 | | - |
182 | 158 | @POST |
183 | 159 | @Path("/{runnerId}/heartbeats") |
184 | 160 | @RateLimited |
185 | 161 | @Operation(operationId = "heartbeat", summary = "Local runner heartbeat", description = "Refresh local runner heartbeat", responses = { |
186 | 162 | @ApiResponse(responseCode = "200", description = "Heartbeat response", content = @Content(schema = @Schema(implementation = LocalRunnerHeartbeatResponse.class))), |
187 | 163 | @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorMessage.class))), |
188 | 164 | @ApiResponse(responseCode = "410", description = "Gone", content = @Content(schema = @Schema(implementation = ErrorMessage.class)))}) |
189 | | - public Response heartbeat(@PathParam("runnerId") UUID runnerId, |
190 | | - @RequestBody(content = @Content(schema = @Schema(implementation = LocalRunnerHeartbeatRequest.class))) LocalRunnerHeartbeatRequest body) { |
| 165 | + public Response heartbeat(@PathParam("runnerId") UUID runnerId) { |
191 | 166 | ensureEnabled(); |
192 | 167 | String workspaceId = requestContext.get().getWorkspaceId(); |
193 | 168 | String userName = requestContext.get().getUserName(); |
194 | | - List<String> capabilities = body != null ? body.capabilities() : null; |
195 | | - LocalRunnerHeartbeatResponse response = runnerService.heartbeat(runnerId, workspaceId, userName, capabilities); |
| 169 | + LocalRunnerHeartbeatResponse response = runnerService.heartbeat(runnerId, workspaceId, userName); |
196 | 170 | return Response.ok(response).build(); |
197 | 171 | } |
198 | 172 |
|
@@ -333,135 +307,6 @@ public Response cancelJob(@PathParam("jobId") UUID jobId) { |
333 | 307 | return Response.noContent().build(); |
334 | 308 | } |
335 | 309 |
|
336 | | - @POST |
337 | | - @Path("/{runnerId}/bridge/commands") |
338 | | - @RateLimited |
339 | | - @Operation(operationId = "submitBridgeCommand", summary = "Submit bridge command", description = "Submit a bridge command for execution by the local daemon", responses = { |
340 | | - @ApiResponse(responseCode = "201", description = "Command submitted", headers = @Header(name = "Location", description = "URI of the command"), content = @Content(schema = @Schema(implementation = BridgeCommandSubmitResponse.class))), |
341 | | - @ApiResponse(responseCode = "404", description = "Runner not found or not connected", content = @Content(schema = @Schema(implementation = ErrorMessage.class))), |
342 | | - @ApiResponse(responseCode = "409", description = "Runner does not support bridge", content = @Content(schema = @Schema(implementation = ErrorMessage.class))), |
343 | | - @ApiResponse(responseCode = "429", description = "Too many requests", content = @Content(schema = @Schema(implementation = ErrorMessage.class)))}) |
344 | | - public Response submitBridgeCommand(@PathParam("runnerId") UUID runnerId, |
345 | | - @RequestBody(content = @Content(schema = @Schema(implementation = BridgeCommandSubmitRequest.class))) @NotNull @Valid BridgeCommandSubmitRequest request, |
346 | | - @Context UriInfo uriInfo) { |
347 | | - ensureEnabled(); |
348 | | - String workspaceId = requestContext.get().getWorkspaceId(); |
349 | | - String userName = requestContext.get().getUserName(); |
350 | | - UUID commandId = runnerService.submitBridgeCommand(runnerId, workspaceId, userName, request); |
351 | | - var uri = uriInfo.getBaseUriBuilder() |
352 | | - .path("v1/private/local-runners/{runnerId}/bridge/commands/{commandId}") |
353 | | - .build(runnerId, commandId); |
354 | | - return Response.created(uri) |
355 | | - .entity(BridgeCommandSubmitResponse.builder().commandId(commandId).build()) |
356 | | - .build(); |
357 | | - } |
358 | | - |
359 | | - @POST |
360 | | - @Path("/{runnerId}/bridge/commands/next") |
361 | | - @Operation(operationId = "nextBridgeCommands", summary = "Poll next bridge commands", description = "Long-poll for pending bridge commands (batch)", responses = { |
362 | | - @ApiResponse(responseCode = "200", description = "Commands batch", content = @Content(schema = @Schema(implementation = BridgeCommandBatchResponse.class))), |
363 | | - @ApiResponse(responseCode = "404", description = "Not found", content = @Content(schema = @Schema(implementation = ErrorMessage.class)))}) |
364 | | - public void nextBridgeCommands(@PathParam("runnerId") UUID runnerId, |
365 | | - @Valid BridgeCommandNextRequest request, |
366 | | - @Suspended AsyncResponse asyncResponse) { |
367 | | - ensureEnabled(); |
368 | | - int maxCommands = request != null ? request.effectiveMaxCommands() : 10; |
369 | | - long pollTimeoutSeconds = runnerConfig.getBridgePollTimeout().toSeconds(); |
370 | | - long bufferSeconds = runnerConfig.getBridgeAsyncTimeoutBuffer().toSeconds(); |
371 | | - asyncResponse.setTimeout(pollTimeoutSeconds + bufferSeconds, TimeUnit.SECONDS); |
372 | | - asyncResponse.setTimeoutHandler( |
373 | | - ar -> ar.resume(Response.ok(BridgeCommandBatchResponse.builder() |
374 | | - .commands(List.of()).build()).build())); |
375 | | - String workspaceId = requestContext.get().getWorkspaceId(); |
376 | | - String userName = requestContext.get().getUserName(); |
377 | | - runnerService.nextBridgeCommands(runnerId, workspaceId, userName, maxCommands) |
378 | | - .map(batch -> Response.ok(batch).build()) |
379 | | - .subscribe( |
380 | | - asyncResponse::resume, |
381 | | - error -> { |
382 | | - if (error instanceof WebApplicationException wae) { |
383 | | - asyncResponse.resume(wae); |
384 | | - } else { |
385 | | - log.error("Error polling bridge commands for runner='{}' workspace='{}'", runnerId, |
386 | | - workspaceId, error); |
387 | | - asyncResponse.resume(Response.serverError().build()); |
388 | | - } |
389 | | - }); |
390 | | - } |
391 | | - |
392 | | - @POST |
393 | | - @Path("/{runnerId}/bridge/commands/{commandId}/results") |
394 | | - @Operation(operationId = "reportBridgeResult", summary = "Report bridge command result", description = "Report bridge command completion or failure", responses = { |
395 | | - @ApiResponse(responseCode = "200", description = "Result accepted"), |
396 | | - @ApiResponse(responseCode = "404", description = "Command not found", content = @Content(schema = @Schema(implementation = ErrorMessage.class))), |
397 | | - @ApiResponse(responseCode = "409", description = "Already completed", content = @Content(schema = @Schema(implementation = ErrorMessage.class)))}) |
398 | | - public Response reportBridgeResult(@PathParam("runnerId") UUID runnerId, |
399 | | - @PathParam("commandId") UUID commandId, |
400 | | - @RequestBody(content = @Content(schema = @Schema(implementation = BridgeCommandResultRequest.class))) @NotNull @Valid BridgeCommandResultRequest request) { |
401 | | - ensureEnabled(); |
402 | | - String workspaceId = requestContext.get().getWorkspaceId(); |
403 | | - String userName = requestContext.get().getUserName(); |
404 | | - runnerService.reportBridgeCommandResult(runnerId, workspaceId, userName, commandId, request); |
405 | | - return Response.ok().build(); |
406 | | - } |
407 | | - |
408 | | - @GET |
409 | | - @Path("/{runnerId}/bridge/commands/{commandId}") |
410 | | - @Operation(operationId = "getBridgeCommand", summary = "Get bridge command", description = "Get bridge command status, optionally long-polling for completion", responses = { |
411 | | - @ApiResponse(responseCode = "200", description = "Command state", content = @Content(schema = @Schema(implementation = BridgeCommand.class))), |
412 | | - @ApiResponse(responseCode = "404", description = "Command not found", content = @Content(schema = @Schema(implementation = ErrorMessage.class)))}) |
413 | | - public void getBridgeCommand(@PathParam("runnerId") UUID runnerId, |
414 | | - @PathParam("commandId") UUID commandId, |
415 | | - @QueryParam("wait") @DefaultValue("false") boolean wait, |
416 | | - @QueryParam("timeout") @DefaultValue("30") int timeout, |
417 | | - @Suspended AsyncResponse asyncResponse) { |
418 | | - ensureEnabled(); |
419 | | - String workspaceId = requestContext.get().getWorkspaceId(); |
420 | | - String userName = requestContext.get().getUserName(); |
421 | | - |
422 | | - if (!wait) { |
423 | | - BridgeCommand command = runnerService.getBridgeCommand(runnerId, workspaceId, userName, commandId); |
424 | | - asyncResponse.resume(Response.ok(command).build()); |
425 | | - return; |
426 | | - } |
427 | | - |
428 | | - int maxTimeout = (int) runnerConfig.getBridgeMaxCommandTimeout().toSeconds(); |
429 | | - int clampedTimeout = Math.min(Math.max(timeout, 1), maxTimeout); |
430 | | - long bufferSeconds = runnerConfig.getBridgeAsyncTimeoutBuffer().toSeconds(); |
431 | | - asyncResponse.setTimeout(clampedTimeout + bufferSeconds, TimeUnit.SECONDS); |
432 | | - asyncResponse.setTimeoutHandler(ar -> { |
433 | | - try { |
434 | | - BridgeCommand cmd = runnerService.getBridgeCommand(runnerId, workspaceId, userName, commandId); |
435 | | - ar.resume(Response.ok(cmd).build()); |
436 | | - } catch (Exception e) { |
437 | | - ar.resume(e); |
438 | | - } |
439 | | - }); |
440 | | - |
441 | | - try { |
442 | | - runnerService.awaitBridgeCommand(runnerId, workspaceId, userName, commandId, clampedTimeout) |
443 | | - .map(cmd -> Response.ok(cmd).build()) |
444 | | - .subscribe( |
445 | | - asyncResponse::resume, |
446 | | - error -> { |
447 | | - if (error instanceof WebApplicationException wae) { |
448 | | - asyncResponse.resume(wae); |
449 | | - } else { |
450 | | - log.error("Error awaiting bridge command='{}' runner='{}' workspace='{}'", |
451 | | - commandId, |
452 | | - runnerId, workspaceId, error); |
453 | | - asyncResponse.resume(Response.serverError().build()); |
454 | | - } |
455 | | - }); |
456 | | - } catch (WebApplicationException wae) { |
457 | | - asyncResponse.resume(wae); |
458 | | - } catch (Exception e) { |
459 | | - log.error("Error setting up bridge command await='{}' runner='{}' workspace='{}'", commandId, |
460 | | - runnerId, workspaceId, e); |
461 | | - asyncResponse.resume(Response.serverError().build()); |
462 | | - } |
463 | | - } |
464 | | - |
465 | 310 | private void ensureEnabled() { |
466 | 311 | if (!runnerConfig.isEnabled()) { |
467 | 312 | throw new WebApplicationException(Response.Status.NOT_IMPLEMENTED); |
|
0 commit comments