From 1f495dfbb7914a859ca8883770d679c9e71d1048 Mon Sep 17 00:00:00 2001 From: Jeremie Fraeys Date: Wed, 4 Mar 2026 13:23:34 -0500 Subject: [PATCH] api: regenerate OpenAPI types and server code - Update openapi.yaml spec - Regenerate server_gen.go with oapi-codegen - Update adapter, routes, and server configuration --- api/openapi.yaml | 734 +++++++++++++++++++++++++++- internal/api/adapter.go | 228 ++++++++- internal/api/routes.go | 39 ++ internal/api/server.go | 4 + internal/api/server_config.go | 13 + internal/api/server_gen.go | 868 ++++++++++++++++++++++++++++------ 6 files changed, 1731 insertions(+), 155 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index c75eddc..d4d8f67 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -62,6 +62,30 @@ paths: schema: type: integer default: 0 + - name: user_id + in: query + schema: + type: string + description: Filter by user who submitted the task + - name: plugin_configs + in: query + schema: + type: object + additionalProperties: + $ref: '#/components/schemas/PluginConfig' + description: Plugin configurations for this task + - name: node_count + in: query + schema: + type: integer + minimum: 1 + default: 1 + description: Number of nodes for multi-node jobs + - name: reservation_id + in: query + schema: + type: string + description: Pre-reserved capacity for this task responses: '200': description: List of tasks @@ -141,6 +165,458 @@ paths: application/json: schema: $ref: '#/components/schemas/QueueStats' + '401': + $ref: '#/components/responses/Unauthorized' + /v1/plugins: + get: + summary: List available plugins + description: Returns all registered plugins and their status + tags: + - Plugins + responses: + '200': + description: List of plugins + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Plugin' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /v1/plugins/{pluginName}: + get: + summary: Get plugin details + description: Returns plugin configuration and status + tags: + - Plugins + parameters: + - name: pluginName + in: path + required: true + schema: + type: string + responses: + '200': + description: Plugin details + content: + application/json: + schema: + $ref: '#/components/schemas/Plugin' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + /v1/plugins/{pluginName}/config: + get: + summary: Get plugin configuration + description: Returns plugin configuration + tags: + - Plugins + parameters: + - name: pluginName + in: path + required: true + schema: + type: string + responses: + '200': + description: Plugin configuration + content: + application/json: + schema: + $ref: '#/components/schemas/PluginConfig' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + put: + summary: Update plugin configuration + description: Update plugin configuration (hot-reload if supported) + tags: + - Plugins + parameters: + - name: pluginName + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PluginConfig' + responses: + '200': + description: Configuration updated + content: + application/json: + schema: + $ref: '#/components/schemas/Plugin' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + '422': + $ref: '#/components/responses/ValidationError' + delete: + summary: Disable/unload plugin + description: Disable plugin (may require restart if plugin requires it) + tags: + - Plugins + parameters: + - name: pluginName + in: path + required: true + schema: + type: string + responses: + '204': + description: Plugin disabled + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + /v1/plugins/{pluginName}/health: + get: + summary: Check plugin health + description: Returns health status of plugin sidecars + tags: + - Plugins + parameters: + - name: pluginName + in: path + required: true + schema: + type: string + responses: + '200': + description: Plugin health + content: + application/json: + schema: + $ref: '#/components/schemas/HealthResponse' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + /v1/scheduler/status: + get: + summary: Get scheduler status + description: Returns queue depths, worker counts, and metrics + tags: + - Scheduler + responses: + '200': + description: Scheduler status + content: + application/json: + schema: + $ref: '#/components/schemas/SchedulerStatus' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /v1/scheduler/status/stream: + get: + summary: SSE stream of scheduler state changes + description: Emits events on queue depth changes, worker connect/disconnect, job transitions + tags: + - Scheduler + produces: + - text/event-stream + responses: + '200': + description: SSE stream + content: + text/event-stream: + schema: + type: string + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /v1/scheduler/workers: + get: + summary: List connected workers + description: Returns all workers and their capabilities + tags: + - Scheduler + responses: + '200': + description: List of workers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Worker' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /v1/scheduler/workers/{workerId}: + get: + summary: Get worker details + description: Returns detailed worker information + tags: + - Scheduler + parameters: + - name: workerId + in: path + required: true + schema: + type: string + responses: + '200': + description: Worker details + content: + application/json: + schema: + $ref: '#/components/schemas/Worker' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + delete: + summary: Disconnect/drain worker + description: Gracefully drain and disconnect a worker + tags: + - Scheduler + parameters: + - name: workerId + in: path + required: true + schema: + type: string + responses: + '204': + description: Worker draining initiated + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + /v1/scheduler/reservations: + get: + summary: List active reservations + description: Returns all active capacity reservations + tags: + - Scheduler + responses: + '200': + description: List of reservations + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Reservation' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + post: + summary: Create reservation + description: Reserve capacity for large jobs + tags: + - Scheduler + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateReservationRequest' + responses: + '201': + description: Reservation created + content: + application/json: + schema: + $ref: '#/components/schemas/Reservation' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '422': + $ref: '#/components/responses/ValidationError' + /v1/scheduler/jobs/{jobId}/priority: + patch: + summary: Update job priority + description: Change the priority of a queued or running job + tags: + - Scheduler + parameters: + - name: jobId + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - priority + properties: + priority: + type: integer + minimum: 1 + maximum: 10 + responses: + '200': + description: Priority updated + content: + application/json: + schema: + $ref: '#/components/schemas/Task' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + /v1/scheduler/jobs/{jobId}/stream: + get: + summary: SSE stream of job progress + description: Emits events on job state transitions and priority changes + tags: + - Scheduler + produces: + - text/event-stream + parameters: + - name: jobId + in: path + required: true + schema: + type: string + responses: + '200': + description: SSE stream + content: + text/event-stream: + schema: + type: string + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '404': + $ref: '#/components/responses/NotFound' + /v1/audit/events: + get: + summary: Query audit events + description: Filter by time range, event type, user + tags: + - Audit + parameters: + - name: from + in: query + schema: + type: string + format: date-time + description: Start timestamp + - name: to + in: query + schema: + type: string + format: date-time + description: End timestamp + - name: event_type + in: query + schema: + type: string + enum: [job_queued, job_started, job_completed, file_access, auth_attempt, plugin_configured, scheduler_drain, audit_verified] + description: Filter by event type + - name: user_id + in: query + schema: + type: string + description: Filter by user + - name: limit + in: query + schema: + type: integer + default: 100 + maximum: 1000 + - name: offset + in: query + schema: + type: integer + default: 0 + responses: + '200': + description: List of audit events + content: + application/json: + schema: + $ref: '#/components/schemas/AuditEventList' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + '429': + $ref: '#/components/responses/RateLimited' + /v1/audit/verify: + post: + summary: Verify audit chain integrity + description: Validates the hash chain for tampering + tags: + - Audit + responses: + '200': + description: Verification result + content: + application/json: + schema: + $ref: '#/components/schemas/VerificationResult' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /v1/audit/chain-root: + get: + summary: Get current chain root hash + description: Returns the latest event hash for external verification + tags: + - Audit + responses: + '200': + description: Chain root hash + content: + application/json: + schema: + type: object + properties: + root_hash: + type: string + timestamp: + type: string + format: date-time + total_events: + type: integer + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' /v1/experiments: get: summary: List experiments @@ -276,6 +752,9 @@ components: type: string output: type: string + entrypoint: + type: string + description: Entrypoint script or command executed for this task snapshot_id: type: string datasets: @@ -294,6 +773,19 @@ components: type: integer max_retries: type: integer + plugin_status: + type: object + additionalProperties: + $ref: '#/components/schemas/PluginStatus' + description: Status of tracking plugins for this task + node_assignments: + type: array + items: + $ref: '#/components/schemas/NodeAssignment' + description: Worker node assignments for multi-node jobs + priority_aged: + type: number + description: Effective priority with aging applied CreateTaskRequest: type: object required: @@ -311,7 +803,14 @@ components: default: 5 args: type: string - description: Command-line arguments for the training script + description: Command-line arguments for the entrypoint + entrypoint: + type: string + description: Entrypoint script or command (e.g., train.py, run.sh, /bin/bash -c "echo hello") + examples: + - train.py + - run.sh + - /bin/bash -c "python train.py --epochs 10" snapshot_id: type: string description: Reference to experiment snapshot @@ -433,6 +932,232 @@ components: image: type: string default: jupyter/pytorch:latest + PluginConfig: + type: object + properties: + enabled: + type: boolean + mode: + type: string + enum: [sidecar, remote, disabled] + image: + type: string + settings: + type: object + additionalProperties: true + PluginStatus: + type: object + properties: + name: + type: string + status: + type: string + enum: [healthy, unhealthy, starting, stopped] + url: + type: string + last_check: + type: string + format: date-time + NodeAssignment: + type: object + properties: + node_id: + type: integer + worker_id: + type: string + slot_assigned: + type: boolean + Plugin: + type: object + properties: + name: + type: string + description: Plugin name + enabled: + type: boolean + description: Whether plugin is enabled + mode: + type: string + enum: [sidecar, remote, disabled] + description: Provisioning mode + status: + $ref: '#/components/schemas/PluginStatus' + description: Current plugin status + config: + $ref: '#/components/schemas/PluginConfig' + requires_restart: + type: boolean + description: Whether plugin requires restart on config change + version: + type: string + description: Plugin version + Worker: + type: object + properties: + id: + type: string + description: Worker unique identifier + connected_at: + type: string + format: date-time + description: When worker connected + last_heartbeat: + type: string + format: date-time + description: Last heartbeat timestamp + capabilities: + type: object + properties: + gpu_count: + type: integer + description: Number of GPUs + gpu_type: + type: string + description: GPU type (e.g., A100, H100) + cpu_cores: + type: integer + memory_gb: + type: integer + slots: + type: object + properties: + batch_available: + type: integer + batch_total: + type: integer + service_available: + type: integer + service_total: + type: integer + active_tasks: + type: array + items: + type: string + description: IDs of tasks currently running on this worker + status: + type: string + enum: [active, draining, offline] + SchedulerStatus: + type: object + properties: + workers_total: + type: integer + workers_active: + type: integer + workers_draining: + type: integer + batch_queue_depth: + type: integer + service_queue_depth: + type: integer + tasks_running: + type: integer + tasks_completed_24h: + type: integer + reservations_active: + type: integer + timestamp: + type: string + format: date-time + Reservation: + type: object + properties: + id: + type: string + user_id: + type: string + gpu_count: + type: integer + gpu_type: + type: string + node_count: + type: integer + expires_at: + type: string + format: date-time + status: + type: string + enum: [active, claimed, expired] + CreateReservationRequest: + type: object + required: + - gpu_count + properties: + gpu_count: + type: integer + minimum: 1 + gpu_type: + type: string + node_count: + type: integer + minimum: 1 + default: 1 + expires_minutes: + type: integer + default: 30 + AuditEvent: + type: object + properties: + timestamp: + type: string + format: date-time + event_type: + type: string + enum: [job_queued, job_started, job_completed, file_access, auth_attempt, plugin_configured, scheduler_drain, audit_verified] + user_id: + type: string + resource: + type: string + description: Resource being acted upon + action: + type: string + description: Action performed + success: + type: boolean + ip_address: + type: string + error: + type: string + prev_hash: + type: string + description: Previous event hash in chain + event_hash: + type: string + description: This event's hash + sequence_num: + type: integer + description: Position in audit chain + metadata: + type: object + additionalProperties: true + AuditEventList: + type: object + properties: + events: + type: array + items: + $ref: '#/components/schemas/AuditEvent' + total: + type: integer + limit: + type: integer + offset: + type: integer + VerificationResult: + type: object + properties: + valid: + type: boolean + total_events: + type: integer + first_tampered: + type: integer + description: Sequence number of first tampered event (if any) + chain_root_hash: + type: string + verified_at: + type: string + format: date-time ErrorResponse: type: object required: @@ -449,6 +1174,13 @@ components: trace_id: type: string description: Support correlation ID + tags: + - name: Plugins + description: Plugin management endpoints + - name: Scheduler + description: Scheduler and worker management + - name: Audit + description: Audit log and chain verification responses: BadRequest: description: Invalid request diff --git a/internal/api/adapter.go b/internal/api/adapter.go index cfa3f3d..e33976e 100644 --- a/internal/api/adapter.go +++ b/internal/api/adapter.go @@ -4,17 +4,23 @@ package api import ( "net/http" + "github.com/jfraeys/fetch_ml/internal/api/audit" "github.com/jfraeys/fetch_ml/internal/api/datasets" "github.com/jfraeys/fetch_ml/internal/api/jobs" "github.com/jfraeys/fetch_ml/internal/api/jupyter" + "github.com/jfraeys/fetch_ml/internal/api/plugins" + sch "github.com/jfraeys/fetch_ml/internal/api/scheduler" "github.com/labstack/echo/v4" ) // HandlerAdapter implements the generated ServerInterface using existing handlers type HandlerAdapter struct { - jobsHandler *jobs.Handler - jupyterHandler *jupyter.Handler - datasetsHandler *datasets.Handler + jobsHandler *jobs.Handler + jupyterHandler *jupyter.Handler + datasetsHandler *datasets.Handler + pluginsHandler *plugins.Handler + schedulerHandler *sch.APIHandler + auditHandler *audit.Handler } // NewHandlerAdapter creates a new handler adapter @@ -22,11 +28,17 @@ func NewHandlerAdapter( jobsHandler *jobs.Handler, jupyterHandler *jupyter.Handler, datasetsHandler *datasets.Handler, + pluginsHandler *plugins.Handler, + schedulerHandler *sch.APIHandler, + auditHandler *audit.Handler, ) *HandlerAdapter { return &HandlerAdapter{ - jobsHandler: jobsHandler, - jupyterHandler: jupyterHandler, - datasetsHandler: datasetsHandler, + jobsHandler: jobsHandler, + jupyterHandler: jupyterHandler, + datasetsHandler: datasetsHandler, + pluginsHandler: pluginsHandler, + schedulerHandler: schedulerHandler, + auditHandler: auditHandler, } } @@ -154,3 +166,207 @@ func (a *HandlerAdapter) GetWs(ctx echo.Context) error { "message": "Use WebSocket protocol to connect to this endpoint", }) } + +// Plugin handlers + +// GetV1Plugins lists all plugins +func (a *HandlerAdapter) GetV1Plugins(ctx echo.Context) error { + if a.pluginsHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Plugin service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.pluginsHandler.GetV1Plugins)(ctx) +} + +// GetV1PluginsPluginName gets plugin details +func (a *HandlerAdapter) GetV1PluginsPluginName(ctx echo.Context, pluginName string) error { + if a.pluginsHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Plugin service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.pluginsHandler.GetV1PluginsPluginName)(ctx) +} + +// GetV1PluginsPluginNameConfig gets plugin configuration +func (a *HandlerAdapter) GetV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error { + if a.pluginsHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Plugin service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.pluginsHandler.GetV1PluginsPluginNameConfig)(ctx) +} + +// PutV1PluginsPluginNameConfig updates plugin configuration +func (a *HandlerAdapter) PutV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error { + if a.pluginsHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Plugin service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.pluginsHandler.PutV1PluginsPluginNameConfig)(ctx) +} + +// DeleteV1PluginsPluginNameConfig disables/unloads plugin +func (a *HandlerAdapter) DeleteV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error { + if a.pluginsHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Plugin service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.pluginsHandler.DeleteV1PluginsPluginNameConfig)(ctx) +} + +// GetV1PluginsPluginNameHealth checks plugin health +func (a *HandlerAdapter) GetV1PluginsPluginNameHealth(ctx echo.Context, pluginName string) error { + if a.pluginsHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Plugin service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.pluginsHandler.GetV1PluginsPluginNameHealth)(ctx) +} + +// Scheduler handlers + +// GetV1SchedulerStatus gets scheduler status +func (a *HandlerAdapter) GetV1SchedulerStatus(ctx echo.Context) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.GetV1SchedulerStatus)(ctx) +} + +// GetV1SchedulerStatusStream gets scheduler status stream (SSE) +func (a *HandlerAdapter) GetV1SchedulerStatusStream(ctx echo.Context) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.GetV1SchedulerStatusStream)(ctx) +} + +// GetV1SchedulerWorkers lists connected workers +func (a *HandlerAdapter) GetV1SchedulerWorkers(ctx echo.Context) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.GetV1SchedulerWorkers)(ctx) +} + +// GetV1SchedulerWorkersWorkerId gets worker details +func (a *HandlerAdapter) GetV1SchedulerWorkersWorkerId(ctx echo.Context, workerId string) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.GetV1SchedulerWorkersWorkerID)(ctx) +} + +// DeleteV1SchedulerWorkersWorkerId drains a worker +func (a *HandlerAdapter) DeleteV1SchedulerWorkersWorkerId(ctx echo.Context, workerId string) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.DeleteV1SchedulerWorkersWorkerID)(ctx) +} + +// GetV1SchedulerReservations lists active reservations +func (a *HandlerAdapter) GetV1SchedulerReservations(ctx echo.Context) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.GetV1SchedulerReservations)(ctx) +} + +// PostV1SchedulerReservations creates a reservation +func (a *HandlerAdapter) PostV1SchedulerReservations(ctx echo.Context) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.PostV1SchedulerReservations)(ctx) +} + +// PatchV1SchedulerJobsJobIdPriority updates job priority +func (a *HandlerAdapter) PatchV1SchedulerJobsJobIdPriority(ctx echo.Context, jobId string) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.PatchV1SchedulerJobsJobIDPriority)(ctx) +} + +// GetV1SchedulerJobsJobIdStream gets job progress stream (SSE) +func (a *HandlerAdapter) GetV1SchedulerJobsJobIdStream(ctx echo.Context, jobId string) error { + if a.schedulerHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Scheduler service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.schedulerHandler.GetV1SchedulerJobsJobIDStream)(ctx) +} + +// Audit handlers + +// GetV1AuditEvents queries audit events +func (a *HandlerAdapter) GetV1AuditEvents(ctx echo.Context, params GetV1AuditEventsParams) error { + if a.auditHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Audit service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.auditHandler.GetV1AuditEvents)(ctx) +} + +// PostV1AuditVerify verifies audit chain integrity +func (a *HandlerAdapter) PostV1AuditVerify(ctx echo.Context) error { + if a.auditHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Audit service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.auditHandler.PostV1AuditVerify)(ctx) +} + +// GetV1AuditChainRoot gets chain root hash +func (a *HandlerAdapter) GetV1AuditChainRoot(ctx echo.Context) error { + if a.auditHandler == nil { + return ctx.JSON(503, map[string]any{ + "error": "Audit service not available", + "code": "SERVICE_UNAVAILABLE", + }) + } + return toHTTPHandler(a.auditHandler.GetV1AuditChainRoot)(ctx) +} diff --git a/internal/api/routes.go b/internal/api/routes.go index ebeceb2..82f852f 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -4,9 +4,12 @@ import ( "net/http" "os" + "github.com/jfraeys/fetch_ml/internal/api/audit" "github.com/jfraeys/fetch_ml/internal/api/datasets" "github.com/jfraeys/fetch_ml/internal/api/jobs" "github.com/jfraeys/fetch_ml/internal/api/jupyter" + "github.com/jfraeys/fetch_ml/internal/api/plugins" + sch "github.com/jfraeys/fetch_ml/internal/api/scheduler" "github.com/jfraeys/fetch_ml/internal/api/ws" "github.com/jfraeys/fetch_ml/internal/prommetrics" "github.com/labstack/echo/v4" @@ -55,8 +58,18 @@ func (s *Server) registerRoutes(mux *http.ServeMux) { // Register OpenAPI-generated routes with Echo router s.registerOpenAPIRoutes(mux, jobsHandler) + // Register scheduler API endpoints + schedulerHandler := sch.NewHandler(s.schedulerHub, s.logger, s.config.BuildAuthConfig()) + mux.HandleFunc("GET /api/v1/workers", schedulerHandler.GetV1SchedulerWorkers) + mux.HandleFunc("GET /api/v1/workers/{workerId}", schedulerHandler.GetV1SchedulerWorkersWorkerID) + mux.HandleFunc("DELETE /api/v1/workers/{workerId}", schedulerHandler.DeleteV1SchedulerWorkersWorkerID) + // Register API documentation endpoint s.registerDocsRoutes(mux) + + // Register OpenAPI spec endpoint + mux.HandleFunc("GET /api/openapi.yaml", ServeOpenAPISpec) + s.logger.Info("OpenAPI spec endpoint registered", "path", "/api/openapi.yaml") } // registerDocsRoutes sets up API documentation serving @@ -90,16 +103,42 @@ func (s *Server) registerOpenAPIRoutes(mux *http.ServeMux, jobsHandler *jobs.Han s.config.DataDir, ) + // Create plugins handler + pluginsHandler := plugins.NewHandler( + s.logger, + s.trackingRegistry, // Need to add this to Server + nil, // Plugin config - can be loaded from config + ) + + // Create scheduler handler + var schedulerHandler *sch.APIHandler + if s.schedulerHub != nil { // Need to add this to Server + schedulerHandler = sch.NewAPIHandler(s.logger, s.schedulerHub) + } + + // Create audit handler + auditHandler := audit.NewHandler(s.logger, nil) // Audit store can be added later + // Create adapter implementing ServerInterface handlerAdapter := NewHandlerAdapter( jobsHandler, jupyterHandler, datasetsHandler, + pluginsHandler, + schedulerHandler, + auditHandler, ) // Register generated OpenAPI routes RegisterHandlers(e, handlerAdapter) + // Add scheduler workers endpoint directly to main mux (not Echo) + if schedulerHandler != nil { + mux.HandleFunc("GET /api/v1/workers", schedulerHandler.GetV1SchedulerWorkers) + mux.HandleFunc("GET /api/v1/workers/{workerId}", schedulerHandler.GetV1SchedulerWorkersWorkerID) + mux.HandleFunc("DELETE /api/v1/workers/{workerId}", schedulerHandler.DeleteV1SchedulerWorkersWorkerID) + } + // Wrap Echo router to work with existing middleware chain echoHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { e.ServeHTTP(w, r) diff --git a/internal/api/server.go b/internal/api/server.go index 62c4a34..68818f4 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -16,7 +16,9 @@ import ( "github.com/jfraeys/fetch_ml/internal/middleware" "github.com/jfraeys/fetch_ml/internal/prommetrics" "github.com/jfraeys/fetch_ml/internal/queue" + "github.com/jfraeys/fetch_ml/internal/scheduler" "github.com/jfraeys/fetch_ml/internal/storage" + "github.com/jfraeys/fetch_ml/internal/tracking" ) // Server represents the API server @@ -32,6 +34,8 @@ type Server struct { auditLogger *audit.Logger promMetrics *prommetrics.Metrics validationMiddleware *apimiddleware.ValidationMiddleware + trackingRegistry *tracking.Registry + schedulerHub *scheduler.SchedulerHub cleanupFuncs []func() } diff --git a/internal/api/server_config.go b/internal/api/server_config.go index 9752ea7..67c23da 100644 --- a/internal/api/server_config.go +++ b/internal/api/server_config.go @@ -8,6 +8,7 @@ import ( "github.com/jfraeys/fetch_ml/internal/auth" "github.com/jfraeys/fetch_ml/internal/config" + "github.com/jfraeys/fetch_ml/internal/crypto/kms" "github.com/jfraeys/fetch_ml/internal/fileutil" "github.com/jfraeys/fetch_ml/internal/logging" "github.com/jfraeys/fetch_ml/internal/storage" @@ -34,6 +35,7 @@ type ServerConfig struct { Redis RedisConfig `yaml:"redis"` Resources config.ResourceConfig `yaml:"resources"` Security SecurityConfig `yaml:"security"` + KMS kms.Config `yaml:"kms,omitempty"` } // ServerSection holds server-specific configuration @@ -204,5 +206,16 @@ func (c *ServerConfig) Validate() error { } } + // Validate KMS configuration if set (defaults to memory provider for development) + if c.KMS.Provider != "" { + if err := c.KMS.Validate(); err != nil { + return fmt.Errorf("invalid KMS configuration: %w", err) + } + } else { + // Default to memory provider for development + c.KMS.Provider = kms.ProviderTypeMemory + c.KMS.Cache = kms.DefaultCacheConfig() + } + return nil } diff --git a/internal/api/server_gen.go b/internal/api/server_gen.go index a559bcc..274ae79 100644 --- a/internal/api/server_gen.go +++ b/internal/api/server_gen.go @@ -4,17 +4,10 @@ package api import ( - "bytes" - "compress/gzip" - "encoding/base64" "fmt" "net/http" - "net/url" - "path" - "strings" "time" - "github.com/getkin/kin-openapi/openapi3" "github.com/labstack/echo/v4" "github.com/oapi-codegen/runtime" ) @@ -23,6 +16,18 @@ const ( ApiKeyAuthScopes = "ApiKeyAuth.Scopes" ) +// Defines values for AuditEventEventType. +const ( + AuditEventEventTypeAuditVerified AuditEventEventType = "audit_verified" + AuditEventEventTypeAuthAttempt AuditEventEventType = "auth_attempt" + AuditEventEventTypeFileAccess AuditEventEventType = "file_access" + AuditEventEventTypeJobCompleted AuditEventEventType = "job_completed" + AuditEventEventTypeJobQueued AuditEventEventType = "job_queued" + AuditEventEventTypeJobStarted AuditEventEventType = "job_started" + AuditEventEventTypePluginConfigured AuditEventEventType = "plugin_configured" + AuditEventEventTypeSchedulerDrain AuditEventEventType = "scheduler_drain" +) + // Defines values for ErrorResponseCode. const ( BADREQUEST ErrorResponseCode = "BAD_REQUEST" @@ -38,16 +43,16 @@ const ( // Defines values for ExperimentStatus. const ( - Active ExperimentStatus = "active" - Archived ExperimentStatus = "archived" - Deleted ExperimentStatus = "deleted" + ExperimentStatusActive ExperimentStatus = "active" + ExperimentStatusArchived ExperimentStatus = "archived" + ExperimentStatusDeleted ExperimentStatus = "deleted" ) // Defines values for HealthResponseStatus. const ( - Degraded HealthResponseStatus = "degraded" - Healthy HealthResponseStatus = "healthy" - Unhealthy HealthResponseStatus = "unhealthy" + HealthResponseStatusDegraded HealthResponseStatus = "degraded" + HealthResponseStatusHealthy HealthResponseStatus = "healthy" + HealthResponseStatusUnhealthy HealthResponseStatus = "unhealthy" ) // Defines values for JupyterServiceStatus. @@ -59,6 +64,35 @@ const ( JupyterServiceStatusStopping JupyterServiceStatus = "stopping" ) +// Defines values for PluginMode. +const ( + PluginModeDisabled PluginMode = "disabled" + PluginModeRemote PluginMode = "remote" + PluginModeSidecar PluginMode = "sidecar" +) + +// Defines values for PluginConfigMode. +const ( + PluginConfigModeDisabled PluginConfigMode = "disabled" + PluginConfigModeRemote PluginConfigMode = "remote" + PluginConfigModeSidecar PluginConfigMode = "sidecar" +) + +// Defines values for PluginStatusStatus. +const ( + PluginStatusStatusHealthy PluginStatusStatus = "healthy" + PluginStatusStatusStarting PluginStatusStatus = "starting" + PluginStatusStatusStopped PluginStatusStatus = "stopped" + PluginStatusStatusUnhealthy PluginStatusStatus = "unhealthy" +) + +// Defines values for ReservationStatus. +const ( + ReservationStatusActive ReservationStatus = "active" + ReservationStatusClaimed ReservationStatus = "claimed" + ReservationStatusExpired ReservationStatus = "expired" +) + // Defines values for TaskStatus. const ( TaskStatusCollecting TaskStatus = "collecting" @@ -69,6 +103,25 @@ const ( TaskStatusRunning TaskStatus = "running" ) +// Defines values for WorkerStatus. +const ( + Active WorkerStatus = "active" + Draining WorkerStatus = "draining" + Offline WorkerStatus = "offline" +) + +// Defines values for GetV1AuditEventsParamsEventType. +const ( + GetV1AuditEventsParamsEventTypeAuditVerified GetV1AuditEventsParamsEventType = "audit_verified" + GetV1AuditEventsParamsEventTypeAuthAttempt GetV1AuditEventsParamsEventType = "auth_attempt" + GetV1AuditEventsParamsEventTypeFileAccess GetV1AuditEventsParamsEventType = "file_access" + GetV1AuditEventsParamsEventTypeJobCompleted GetV1AuditEventsParamsEventType = "job_completed" + GetV1AuditEventsParamsEventTypeJobQueued GetV1AuditEventsParamsEventType = "job_queued" + GetV1AuditEventsParamsEventTypeJobStarted GetV1AuditEventsParamsEventType = "job_started" + GetV1AuditEventsParamsEventTypePluginConfigured GetV1AuditEventsParamsEventType = "plugin_configured" + GetV1AuditEventsParamsEventTypeSchedulerDrain GetV1AuditEventsParamsEventType = "scheduler_drain" +) + // Defines values for GetV1TasksParamsStatus. const ( Completed GetV1TasksParamsStatus = "completed" @@ -77,15 +130,59 @@ const ( Running GetV1TasksParamsStatus = "running" ) +// AuditEvent defines model for AuditEvent. +type AuditEvent struct { + // Action Action performed + Action *string `json:"action,omitempty"` + Error *string `json:"error,omitempty"` + + // EventHash This event's hash + EventHash *string `json:"event_hash,omitempty"` + EventType *AuditEventEventType `json:"event_type,omitempty"` + IpAddress *string `json:"ip_address,omitempty"` + Metadata *map[string]interface{} `json:"metadata,omitempty"` + + // PrevHash Previous event hash in chain + PrevHash *string `json:"prev_hash,omitempty"` + + // Resource Resource being acted upon + Resource *string `json:"resource,omitempty"` + + // SequenceNum Position in audit chain + SequenceNum *int `json:"sequence_num,omitempty"` + Success *bool `json:"success,omitempty"` + Timestamp *time.Time `json:"timestamp,omitempty"` + UserId *string `json:"user_id,omitempty"` +} + +// AuditEventEventType defines model for AuditEvent.EventType. +type AuditEventEventType string + +// AuditEventList defines model for AuditEventList. +type AuditEventList struct { + Events *[]AuditEvent `json:"events,omitempty"` + Limit *int `json:"limit,omitempty"` + Offset *int `json:"offset,omitempty"` + Total *int `json:"total,omitempty"` +} + // CreateExperimentRequest defines model for CreateExperimentRequest. type CreateExperimentRequest struct { Description *string `json:"description,omitempty"` Name string `json:"name"` } +// CreateReservationRequest defines model for CreateReservationRequest. +type CreateReservationRequest struct { + ExpiresMinutes *int `json:"expires_minutes,omitempty"` + GpuCount int `json:"gpu_count"` + GpuType *string `json:"gpu_type,omitempty"` + NodeCount *int `json:"node_count,omitempty"` +} + // CreateTaskRequest defines model for CreateTaskRequest. type CreateTaskRequest struct { - // Args Command-line arguments for the training script + // Args Command-line arguments for the entrypoint Args *string `json:"args,omitempty"` // Cpu CPU cores requested @@ -93,6 +190,9 @@ type CreateTaskRequest struct { DatasetSpecs *[]DatasetSpec `json:"dataset_specs,omitempty"` Datasets *[]string `json:"datasets,omitempty"` + // Entrypoint Entrypoint script or command (e.g., train.py, run.sh, /bin/bash -c "echo hello") + Entrypoint *string `json:"entrypoint,omitempty"` + // Gpu GPUs requested Gpu *int `json:"gpu,omitempty"` @@ -165,6 +265,59 @@ type JupyterService struct { // JupyterServiceStatus defines model for JupyterService.Status. type JupyterServiceStatus string +// NodeAssignment defines model for NodeAssignment. +type NodeAssignment struct { + NodeId *int `json:"node_id,omitempty"` + SlotAssigned *bool `json:"slot_assigned,omitempty"` + WorkerId *string `json:"worker_id,omitempty"` +} + +// Plugin defines model for Plugin. +type Plugin struct { + Config *PluginConfig `json:"config,omitempty"` + + // Enabled Whether plugin is enabled + Enabled *bool `json:"enabled,omitempty"` + + // Mode Provisioning mode + Mode *PluginMode `json:"mode,omitempty"` + + // Name Plugin name + Name *string `json:"name,omitempty"` + + // RequiresRestart Whether plugin requires restart on config change + RequiresRestart *bool `json:"requires_restart,omitempty"` + Status *PluginStatus `json:"status,omitempty"` + + // Version Plugin version + Version *string `json:"version,omitempty"` +} + +// PluginMode Provisioning mode +type PluginMode string + +// PluginConfig defines model for PluginConfig. +type PluginConfig struct { + Enabled *bool `json:"enabled,omitempty"` + Image *string `json:"image,omitempty"` + Mode *PluginConfigMode `json:"mode,omitempty"` + Settings *map[string]interface{} `json:"settings,omitempty"` +} + +// PluginConfigMode defines model for PluginConfig.Mode. +type PluginConfigMode string + +// PluginStatus defines model for PluginStatus. +type PluginStatus struct { + LastCheck *time.Time `json:"last_check,omitempty"` + Name *string `json:"name,omitempty"` + Status *PluginStatusStatus `json:"status,omitempty"` + Url *string `json:"url,omitempty"` +} + +// PluginStatusStatus defines model for PluginStatus.Status. +type PluginStatusStatus string + // QueueStats defines model for QueueStats. type QueueStats struct { // Completed Tasks completed today @@ -183,6 +336,33 @@ type QueueStats struct { Workers *int `json:"workers,omitempty"` } +// Reservation defines model for Reservation. +type Reservation struct { + ExpiresAt *time.Time `json:"expires_at,omitempty"` + GpuCount *int `json:"gpu_count,omitempty"` + GpuType *string `json:"gpu_type,omitempty"` + Id *string `json:"id,omitempty"` + NodeCount *int `json:"node_count,omitempty"` + Status *ReservationStatus `json:"status,omitempty"` + UserId *string `json:"user_id,omitempty"` +} + +// ReservationStatus defines model for Reservation.Status. +type ReservationStatus string + +// SchedulerStatus defines model for SchedulerStatus. +type SchedulerStatus struct { + BatchQueueDepth *int `json:"batch_queue_depth,omitempty"` + ReservationsActive *int `json:"reservations_active,omitempty"` + ServiceQueueDepth *int `json:"service_queue_depth,omitempty"` + TasksCompleted24h *int `json:"tasks_completed_24h,omitempty"` + TasksRunning *int `json:"tasks_running,omitempty"` + Timestamp *time.Time `json:"timestamp,omitempty"` + WorkersActive *int `json:"workers_active,omitempty"` + WorkersDraining *int `json:"workers_draining,omitempty"` + WorkersTotal *int `json:"workers_total,omitempty"` +} + // StartJupyterRequest defines model for StartJupyterRequest. type StartJupyterRequest struct { Image *string `json:"image,omitempty"` @@ -196,22 +376,34 @@ type Task struct { CreatedAt *time.Time `json:"created_at,omitempty"` Datasets *[]string `json:"datasets,omitempty"` EndedAt *time.Time `json:"ended_at,omitempty"` - Error *string `json:"error,omitempty"` - Gpu *int `json:"gpu,omitempty"` + + // Entrypoint Entrypoint script or command executed for this task + Entrypoint *string `json:"entrypoint,omitempty"` + Error *string `json:"error,omitempty"` + Gpu *int `json:"gpu,omitempty"` // Id Unique task identifier - Id *string `json:"id,omitempty"` - JobName *string `json:"job_name,omitempty"` - MaxRetries *int `json:"max_retries,omitempty"` - MemoryGb *int `json:"memory_gb,omitempty"` - Output *string `json:"output,omitempty"` - Priority *int `json:"priority,omitempty"` - RetryCount *int `json:"retry_count,omitempty"` - SnapshotId *string `json:"snapshot_id,omitempty"` - StartedAt *time.Time `json:"started_at,omitempty"` - Status *TaskStatus `json:"status,omitempty"` - UserId *string `json:"user_id,omitempty"` - WorkerId *string `json:"worker_id,omitempty"` + Id *string `json:"id,omitempty"` + JobName *string `json:"job_name,omitempty"` + MaxRetries *int `json:"max_retries,omitempty"` + MemoryGb *int `json:"memory_gb,omitempty"` + + // NodeAssignments Worker node assignments for multi-node jobs + NodeAssignments *[]NodeAssignment `json:"node_assignments,omitempty"` + Output *string `json:"output,omitempty"` + + // PluginStatus Status of tracking plugins for this task + PluginStatus *map[string]PluginStatus `json:"plugin_status,omitempty"` + Priority *int `json:"priority,omitempty"` + + // PriorityAged Effective priority with aging applied + PriorityAged *float32 `json:"priority_aged,omitempty"` + RetryCount *int `json:"retry_count,omitempty"` + SnapshotId *string `json:"snapshot_id,omitempty"` + StartedAt *time.Time `json:"started_at,omitempty"` + Status *TaskStatus `json:"status,omitempty"` + UserId *string `json:"user_id,omitempty"` + WorkerId *string `json:"worker_id,omitempty"` } // TaskStatus defines model for Task.Status. @@ -225,9 +417,58 @@ type TaskList struct { Total *int `json:"total,omitempty"` } +// VerificationResult defines model for VerificationResult. +type VerificationResult struct { + ChainRootHash *string `json:"chain_root_hash,omitempty"` + + // FirstTampered Sequence number of first tampered event (if any) + FirstTampered *int `json:"first_tampered,omitempty"` + TotalEvents *int `json:"total_events,omitempty"` + Valid *bool `json:"valid,omitempty"` + VerifiedAt *time.Time `json:"verified_at,omitempty"` +} + +// Worker defines model for Worker. +type Worker struct { + // ActiveTasks IDs of tasks currently running on this worker + ActiveTasks *[]string `json:"active_tasks,omitempty"` + Capabilities *struct { + CpuCores *int `json:"cpu_cores,omitempty"` + + // GpuCount Number of GPUs + GpuCount *int `json:"gpu_count,omitempty"` + + // GpuType GPU type (e.g., A100, H100) + GpuType *string `json:"gpu_type,omitempty"` + MemoryGb *int `json:"memory_gb,omitempty"` + } `json:"capabilities,omitempty"` + + // ConnectedAt When worker connected + ConnectedAt *time.Time `json:"connected_at,omitempty"` + + // Id Worker unique identifier + Id *string `json:"id,omitempty"` + + // LastHeartbeat Last heartbeat timestamp + LastHeartbeat *time.Time `json:"last_heartbeat,omitempty"` + Slots *struct { + BatchAvailable *int `json:"batch_available,omitempty"` + BatchTotal *int `json:"batch_total,omitempty"` + ServiceAvailable *int `json:"service_available,omitempty"` + ServiceTotal *int `json:"service_total,omitempty"` + } `json:"slots,omitempty"` + Status *WorkerStatus `json:"status,omitempty"` +} + +// WorkerStatus defines model for Worker.Status. +type WorkerStatus string + // BadRequest defines model for BadRequest. type BadRequest = ErrorResponse +// Forbidden defines model for Forbidden. +type Forbidden = ErrorResponse + // NotFound defines model for NotFound. type NotFound = ErrorResponse @@ -240,11 +481,48 @@ type Unauthorized = ErrorResponse // ValidationError defines model for ValidationError. type ValidationError = ErrorResponse +// GetV1AuditEventsParams defines parameters for GetV1AuditEvents. +type GetV1AuditEventsParams struct { + // From Start timestamp + From *time.Time `form:"from,omitempty" json:"from,omitempty"` + + // To End timestamp + To *time.Time `form:"to,omitempty" json:"to,omitempty"` + + // EventType Filter by event type + EventType *GetV1AuditEventsParamsEventType `form:"event_type,omitempty" json:"event_type,omitempty"` + + // UserId Filter by user + UserId *string `form:"user_id,omitempty" json:"user_id,omitempty"` + Limit *int `form:"limit,omitempty" json:"limit,omitempty"` + Offset *int `form:"offset,omitempty" json:"offset,omitempty"` +} + +// GetV1AuditEventsParamsEventType defines parameters for GetV1AuditEvents. +type GetV1AuditEventsParamsEventType string + +// PatchV1SchedulerJobsJobIdPriorityJSONBody defines parameters for PatchV1SchedulerJobsJobIdPriority. +type PatchV1SchedulerJobsJobIdPriorityJSONBody struct { + Priority int `json:"priority"` +} + // GetV1TasksParams defines parameters for GetV1Tasks. type GetV1TasksParams struct { Status *GetV1TasksParamsStatus `form:"status,omitempty" json:"status,omitempty"` Limit *int `form:"limit,omitempty" json:"limit,omitempty"` Offset *int `form:"offset,omitempty" json:"offset,omitempty"` + + // UserId Filter by user who submitted the task + UserId *string `form:"user_id,omitempty" json:"user_id,omitempty"` + + // PluginConfigs Plugin configurations for this task + PluginConfigs *map[string]PluginConfig `form:"plugin_configs,omitempty" json:"plugin_configs,omitempty"` + + // NodeCount Number of nodes for multi-node jobs + NodeCount *int `form:"node_count,omitempty" json:"node_count,omitempty"` + + // ReservationId Pre-reserved capacity for this task + ReservationId *string `form:"reservation_id,omitempty" json:"reservation_id,omitempty"` } // GetV1TasksParamsStatus defines parameters for GetV1Tasks. @@ -256,6 +534,15 @@ type PostV1ExperimentsJSONRequestBody = CreateExperimentRequest // PostV1JupyterServicesJSONRequestBody defines body for PostV1JupyterServices for application/json ContentType. type PostV1JupyterServicesJSONRequestBody = StartJupyterRequest +// PutV1PluginsPluginNameConfigJSONRequestBody defines body for PutV1PluginsPluginNameConfig for application/json ContentType. +type PutV1PluginsPluginNameConfigJSONRequestBody = PluginConfig + +// PatchV1SchedulerJobsJobIdPriorityJSONRequestBody defines body for PatchV1SchedulerJobsJobIdPriority for application/json ContentType. +type PatchV1SchedulerJobsJobIdPriorityJSONRequestBody PatchV1SchedulerJobsJobIdPriorityJSONBody + +// PostV1SchedulerReservationsJSONRequestBody defines body for PostV1SchedulerReservations for application/json ContentType. +type PostV1SchedulerReservationsJSONRequestBody = CreateReservationRequest + // PostV1TasksJSONRequestBody defines body for PostV1Tasks for application/json ContentType. type PostV1TasksJSONRequestBody = CreateTaskRequest @@ -264,6 +551,15 @@ type ServerInterface interface { // Health check // (GET /health) GetHealth(ctx echo.Context) error + // Get current chain root hash + // (GET /v1/audit/chain-root) + GetV1AuditChainRoot(ctx echo.Context) error + // Query audit events + // (GET /v1/audit/events) + GetV1AuditEvents(ctx echo.Context, params GetV1AuditEventsParams) error + // Verify audit chain integrity + // (POST /v1/audit/verify) + PostV1AuditVerify(ctx echo.Context) error // List experiments // (GET /v1/experiments) GetV1Experiments(ctx echo.Context) error @@ -279,9 +575,54 @@ type ServerInterface interface { // Stop Jupyter service // (DELETE /v1/jupyter/services/{serviceId}) DeleteV1JupyterServicesServiceId(ctx echo.Context, serviceId string) error + // List available plugins + // (GET /v1/plugins) + GetV1Plugins(ctx echo.Context) error + // Get plugin details + // (GET /v1/plugins/{pluginName}) + GetV1PluginsPluginName(ctx echo.Context, pluginName string) error + // Disable/unload plugin + // (DELETE /v1/plugins/{pluginName}/config) + DeleteV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error + // Get plugin configuration + // (GET /v1/plugins/{pluginName}/config) + GetV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error + // Update plugin configuration + // (PUT /v1/plugins/{pluginName}/config) + PutV1PluginsPluginNameConfig(ctx echo.Context, pluginName string) error + // Check plugin health + // (GET /v1/plugins/{pluginName}/health) + GetV1PluginsPluginNameHealth(ctx echo.Context, pluginName string) error // Queue status // (GET /v1/queue) GetV1Queue(ctx echo.Context) error + // Update job priority + // (PATCH /v1/scheduler/jobs/{jobId}/priority) + PatchV1SchedulerJobsJobIdPriority(ctx echo.Context, jobId string) error + // SSE stream of job progress + // (GET /v1/scheduler/jobs/{jobId}/stream) + GetV1SchedulerJobsJobIdStream(ctx echo.Context, jobId string) error + // List active reservations + // (GET /v1/scheduler/reservations) + GetV1SchedulerReservations(ctx echo.Context) error + // Create reservation + // (POST /v1/scheduler/reservations) + PostV1SchedulerReservations(ctx echo.Context) error + // Get scheduler status + // (GET /v1/scheduler/status) + GetV1SchedulerStatus(ctx echo.Context) error + // SSE stream of scheduler state changes + // (GET /v1/scheduler/status/stream) + GetV1SchedulerStatusStream(ctx echo.Context) error + // List connected workers + // (GET /v1/scheduler/workers) + GetV1SchedulerWorkers(ctx echo.Context) error + // Disconnect/drain worker + // (DELETE /v1/scheduler/workers/{workerId}) + DeleteV1SchedulerWorkersWorkerId(ctx echo.Context, workerId string) error + // Get worker details + // (GET /v1/scheduler/workers/{workerId}) + GetV1SchedulerWorkersWorkerId(ctx echo.Context, workerId string) error // List tasks // (GET /v1/tasks) GetV1Tasks(ctx echo.Context, params GetV1TasksParams) error @@ -313,6 +654,83 @@ func (w *ServerInterfaceWrapper) GetHealth(ctx echo.Context) error { return err } +// GetV1AuditChainRoot converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1AuditChainRoot(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1AuditChainRoot(ctx) + return err +} + +// GetV1AuditEvents converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1AuditEvents(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Parameter object where we will unmarshal all parameters from the context + var params GetV1AuditEventsParams + // ------------- Optional query parameter "from" ------------- + + err = runtime.BindQueryParameter("form", true, false, "from", ctx.QueryParams(), ¶ms.From) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter from: %s", err)) + } + + // ------------- Optional query parameter "to" ------------- + + err = runtime.BindQueryParameter("form", true, false, "to", ctx.QueryParams(), ¶ms.To) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter to: %s", err)) + } + + // ------------- Optional query parameter "event_type" ------------- + + err = runtime.BindQueryParameter("form", true, false, "event_type", ctx.QueryParams(), ¶ms.EventType) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter event_type: %s", err)) + } + + // ------------- Optional query parameter "user_id" ------------- + + err = runtime.BindQueryParameter("form", true, false, "user_id", ctx.QueryParams(), ¶ms.UserId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter user_id: %s", err)) + } + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", ctx.QueryParams(), ¶ms.Limit) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter limit: %s", err)) + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", ctx.QueryParams(), ¶ms.Offset) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1AuditEvents(ctx, params) + return err +} + +// PostV1AuditVerify converts echo context to params. +func (w *ServerInterfaceWrapper) PostV1AuditVerify(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PostV1AuditVerify(ctx) + return err +} + // GetV1Experiments converts echo context to params. func (w *ServerInterfaceWrapper) GetV1Experiments(ctx echo.Context) error { var err error @@ -375,6 +793,107 @@ func (w *ServerInterfaceWrapper) DeleteV1JupyterServicesServiceId(ctx echo.Conte return err } +// GetV1Plugins converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1Plugins(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1Plugins(ctx) + return err +} + +// GetV1PluginsPluginName converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1PluginsPluginName(ctx echo.Context) error { + var err error + // ------------- Path parameter "pluginName" ------------- + var pluginName string + + err = runtime.BindStyledParameterWithOptions("simple", "pluginName", ctx.Param("pluginName"), &pluginName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter pluginName: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1PluginsPluginName(ctx, pluginName) + return err +} + +// DeleteV1PluginsPluginNameConfig converts echo context to params. +func (w *ServerInterfaceWrapper) DeleteV1PluginsPluginNameConfig(ctx echo.Context) error { + var err error + // ------------- Path parameter "pluginName" ------------- + var pluginName string + + err = runtime.BindStyledParameterWithOptions("simple", "pluginName", ctx.Param("pluginName"), &pluginName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter pluginName: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeleteV1PluginsPluginNameConfig(ctx, pluginName) + return err +} + +// GetV1PluginsPluginNameConfig converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1PluginsPluginNameConfig(ctx echo.Context) error { + var err error + // ------------- Path parameter "pluginName" ------------- + var pluginName string + + err = runtime.BindStyledParameterWithOptions("simple", "pluginName", ctx.Param("pluginName"), &pluginName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter pluginName: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1PluginsPluginNameConfig(ctx, pluginName) + return err +} + +// PutV1PluginsPluginNameConfig converts echo context to params. +func (w *ServerInterfaceWrapper) PutV1PluginsPluginNameConfig(ctx echo.Context) error { + var err error + // ------------- Path parameter "pluginName" ------------- + var pluginName string + + err = runtime.BindStyledParameterWithOptions("simple", "pluginName", ctx.Param("pluginName"), &pluginName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter pluginName: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PutV1PluginsPluginNameConfig(ctx, pluginName) + return err +} + +// GetV1PluginsPluginNameHealth converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1PluginsPluginNameHealth(ctx echo.Context) error { + var err error + // ------------- Path parameter "pluginName" ------------- + var pluginName string + + err = runtime.BindStyledParameterWithOptions("simple", "pluginName", ctx.Param("pluginName"), &pluginName, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter pluginName: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1PluginsPluginNameHealth(ctx, pluginName) + return err +} + // GetV1Queue converts echo context to params. func (w *ServerInterfaceWrapper) GetV1Queue(ctx echo.Context) error { var err error @@ -386,6 +905,133 @@ func (w *ServerInterfaceWrapper) GetV1Queue(ctx echo.Context) error { return err } +// PatchV1SchedulerJobsJobIdPriority converts echo context to params. +func (w *ServerInterfaceWrapper) PatchV1SchedulerJobsJobIdPriority(ctx echo.Context) error { + var err error + // ------------- Path parameter "jobId" ------------- + var jobId string + + err = runtime.BindStyledParameterWithOptions("simple", "jobId", ctx.Param("jobId"), &jobId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter jobId: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PatchV1SchedulerJobsJobIdPriority(ctx, jobId) + return err +} + +// GetV1SchedulerJobsJobIdStream converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1SchedulerJobsJobIdStream(ctx echo.Context) error { + var err error + // ------------- Path parameter "jobId" ------------- + var jobId string + + err = runtime.BindStyledParameterWithOptions("simple", "jobId", ctx.Param("jobId"), &jobId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter jobId: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1SchedulerJobsJobIdStream(ctx, jobId) + return err +} + +// GetV1SchedulerReservations converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1SchedulerReservations(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1SchedulerReservations(ctx) + return err +} + +// PostV1SchedulerReservations converts echo context to params. +func (w *ServerInterfaceWrapper) PostV1SchedulerReservations(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PostV1SchedulerReservations(ctx) + return err +} + +// GetV1SchedulerStatus converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1SchedulerStatus(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1SchedulerStatus(ctx) + return err +} + +// GetV1SchedulerStatusStream converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1SchedulerStatusStream(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1SchedulerStatusStream(ctx) + return err +} + +// GetV1SchedulerWorkers converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1SchedulerWorkers(ctx echo.Context) error { + var err error + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1SchedulerWorkers(ctx) + return err +} + +// DeleteV1SchedulerWorkersWorkerId converts echo context to params. +func (w *ServerInterfaceWrapper) DeleteV1SchedulerWorkersWorkerId(ctx echo.Context) error { + var err error + // ------------- Path parameter "workerId" ------------- + var workerId string + + err = runtime.BindStyledParameterWithOptions("simple", "workerId", ctx.Param("workerId"), &workerId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter workerId: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.DeleteV1SchedulerWorkersWorkerId(ctx, workerId) + return err +} + +// GetV1SchedulerWorkersWorkerId converts echo context to params. +func (w *ServerInterfaceWrapper) GetV1SchedulerWorkersWorkerId(ctx echo.Context) error { + var err error + // ------------- Path parameter "workerId" ------------- + var workerId string + + err = runtime.BindStyledParameterWithOptions("simple", "workerId", ctx.Param("workerId"), &workerId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter workerId: %s", err)) + } + + ctx.Set(ApiKeyAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetV1SchedulerWorkersWorkerId(ctx, workerId) + return err +} + // GetV1Tasks converts echo context to params. func (w *ServerInterfaceWrapper) GetV1Tasks(ctx echo.Context) error { var err error @@ -415,6 +1061,34 @@ func (w *ServerInterfaceWrapper) GetV1Tasks(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter offset: %s", err)) } + // ------------- Optional query parameter "user_id" ------------- + + err = runtime.BindQueryParameter("form", true, false, "user_id", ctx.QueryParams(), ¶ms.UserId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter user_id: %s", err)) + } + + // ------------- Optional query parameter "plugin_configs" ------------- + + err = runtime.BindQueryParameter("form", true, false, "plugin_configs", ctx.QueryParams(), ¶ms.PluginConfigs) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter plugin_configs: %s", err)) + } + + // ------------- Optional query parameter "node_count" ------------- + + err = runtime.BindQueryParameter("form", true, false, "node_count", ctx.QueryParams(), ¶ms.NodeCount) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter node_count: %s", err)) + } + + // ------------- Optional query parameter "reservation_id" ------------- + + err = runtime.BindQueryParameter("form", true, false, "reservation_id", ctx.QueryParams(), ¶ms.ReservationId) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter reservation_id: %s", err)) + } + // Invoke the callback with all the unmarshaled arguments err = w.Handler.GetV1Tasks(ctx, params) return err @@ -507,12 +1181,30 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL } router.GET(baseURL+"/health", wrapper.GetHealth) + router.GET(baseURL+"/v1/audit/chain-root", wrapper.GetV1AuditChainRoot) + router.GET(baseURL+"/v1/audit/events", wrapper.GetV1AuditEvents) + router.POST(baseURL+"/v1/audit/verify", wrapper.PostV1AuditVerify) router.GET(baseURL+"/v1/experiments", wrapper.GetV1Experiments) router.POST(baseURL+"/v1/experiments", wrapper.PostV1Experiments) router.GET(baseURL+"/v1/jupyter/services", wrapper.GetV1JupyterServices) router.POST(baseURL+"/v1/jupyter/services", wrapper.PostV1JupyterServices) router.DELETE(baseURL+"/v1/jupyter/services/:serviceId", wrapper.DeleteV1JupyterServicesServiceId) + router.GET(baseURL+"/v1/plugins", wrapper.GetV1Plugins) + router.GET(baseURL+"/v1/plugins/:pluginName", wrapper.GetV1PluginsPluginName) + router.DELETE(baseURL+"/v1/plugins/:pluginName/config", wrapper.DeleteV1PluginsPluginNameConfig) + router.GET(baseURL+"/v1/plugins/:pluginName/config", wrapper.GetV1PluginsPluginNameConfig) + router.PUT(baseURL+"/v1/plugins/:pluginName/config", wrapper.PutV1PluginsPluginNameConfig) + router.GET(baseURL+"/v1/plugins/:pluginName/health", wrapper.GetV1PluginsPluginNameHealth) router.GET(baseURL+"/v1/queue", wrapper.GetV1Queue) + router.PATCH(baseURL+"/v1/scheduler/jobs/:jobId/priority", wrapper.PatchV1SchedulerJobsJobIdPriority) + router.GET(baseURL+"/v1/scheduler/jobs/:jobId/stream", wrapper.GetV1SchedulerJobsJobIdStream) + router.GET(baseURL+"/v1/scheduler/reservations", wrapper.GetV1SchedulerReservations) + router.POST(baseURL+"/v1/scheduler/reservations", wrapper.PostV1SchedulerReservations) + router.GET(baseURL+"/v1/scheduler/status", wrapper.GetV1SchedulerStatus) + router.GET(baseURL+"/v1/scheduler/status/stream", wrapper.GetV1SchedulerStatusStream) + router.GET(baseURL+"/v1/scheduler/workers", wrapper.GetV1SchedulerWorkers) + router.DELETE(baseURL+"/v1/scheduler/workers/:workerId", wrapper.DeleteV1SchedulerWorkersWorkerId) + router.GET(baseURL+"/v1/scheduler/workers/:workerId", wrapper.GetV1SchedulerWorkersWorkerId) router.GET(baseURL+"/v1/tasks", wrapper.GetV1Tasks) router.POST(baseURL+"/v1/tasks", wrapper.PostV1Tasks) router.DELETE(baseURL+"/v1/tasks/:taskId", wrapper.DeleteV1TasksTaskId) @@ -520,123 +1212,3 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/ws", wrapper.GetWs) } - -// Base64 encoded, gzipped, json marshaled Swagger object -var swaggerSpec = []string{ - - "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", -} - -// GetSwagger returns the content of the embedded swagger specification file -// or error if failed to decode -func decodeSpec() ([]byte, error) { - zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) - if err != nil { - return nil, fmt.Errorf("error base64 decoding spec: %w", err) - } - zr, err := gzip.NewReader(bytes.NewReader(zipped)) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %w", err) - } - var buf bytes.Buffer - _, err = buf.ReadFrom(zr) - if err != nil { - return nil, fmt.Errorf("error decompressing spec: %w", err) - } - - return buf.Bytes(), nil -} - -var rawSpec = decodeSpecCached() - -// a naive cached of a decoded swagger spec -func decodeSpecCached() func() ([]byte, error) { - data, err := decodeSpec() - return func() ([]byte, error) { - return data, err - } -} - -// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. -func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { - res := make(map[string]func() ([]byte, error)) - if len(pathToFile) > 0 { - res[pathToFile] = rawSpec - } - - return res -} - -// GetSwagger returns the Swagger specification corresponding to the generated code -// in this file. The external references of Swagger specification are resolved. -// The logic of resolving external references is tightly connected to "import-mapping" feature. -// Externally referenced files must be embedded in the corresponding golang packages. -// Urls can be supported but this task was out of the scope. -func GetSwagger() (swagger *openapi3.T, err error) { - resolvePath := PathToRawSpec("") - - loader := openapi3.NewLoader() - loader.IsExternalRefsAllowed = true - loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { - pathToFile := url.String() - pathToFile = path.Clean(pathToFile) - getSpec, ok := resolvePath[pathToFile] - if !ok { - err1 := fmt.Errorf("path not found: %s", pathToFile) - return nil, err1 - } - return getSpec() - } - var specData []byte - specData, err = rawSpec() - if err != nil { - return - } - swagger, err = loader.LoadFromData(specData) - if err != nil { - return - } - return -}