api: regenerate OpenAPI types and server code
- Update openapi.yaml spec - Regenerate server_gen.go with oapi-codegen - Update adapter, routes, and server configuration
This commit is contained in:
parent
743bc4be3b
commit
1f495dfbb7
6 changed files with 1731 additions and 155 deletions
734
api/openapi.yaml
734
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue