mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 05:19:05 +01:00
Move identical parts of stopJob routines to new function
- No solution for two swagger doc annotations for one function (ie function handles two inputs) found - Also: Delete handwritten openapi.yaml
This commit is contained in:
parent
b96c515c2c
commit
c9184a7575
221
api/openapi.yaml
221
api/openapi.yaml
@ -1,221 +0,0 @@
|
|||||||
#
|
|
||||||
# ClusterCockpit's API spec can be exported via:
|
|
||||||
# docker exec -it cc-php php bin/console api:openapi:export --yaml
|
|
||||||
#
|
|
||||||
# This spec is written by hand and hopefully up to date with the API.
|
|
||||||
#
|
|
||||||
|
|
||||||
openapi: 3.0.3
|
|
||||||
info:
|
|
||||||
title: 'ClusterCockpit REST API'
|
|
||||||
description: 'API for batch job control'
|
|
||||||
version: 0.0.2
|
|
||||||
servers:
|
|
||||||
- url: /
|
|
||||||
description: ''
|
|
||||||
paths:
|
|
||||||
'/api/jobs/':
|
|
||||||
get:
|
|
||||||
operationId: 'getJobs'
|
|
||||||
summary: 'List all jobs'
|
|
||||||
description: 'Get a list of all jobs. Filters can be applied using query parameters.'
|
|
||||||
parameters:
|
|
||||||
- name: state
|
|
||||||
in: query
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
enum: ["running", "completed", "failed", "canceled", "stopped", "timeout"]
|
|
||||||
- name: cluster
|
|
||||||
in: query
|
|
||||||
schema: { type: string }
|
|
||||||
- name: start-time
|
|
||||||
description: 'Syntax: "<from>-<to>", where <from> and <to> are unix timestamps in seconds'
|
|
||||||
in: query
|
|
||||||
schema: { type: string }
|
|
||||||
- name: page
|
|
||||||
in: query
|
|
||||||
schema: { type: integer }
|
|
||||||
- name: items-per-page
|
|
||||||
in: query
|
|
||||||
schema: { type: integer }
|
|
||||||
- name: with-metadata
|
|
||||||
in: query
|
|
||||||
schema: { type: boolean }
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 'Array of jobs'
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
jobs:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/Job'
|
|
||||||
400:
|
|
||||||
description: 'Bad Request'
|
|
||||||
'/api/jobs/tag_job/{id}':
|
|
||||||
post:
|
|
||||||
operationId: 'tagJob'
|
|
||||||
summary: 'Add a tag to a job'
|
|
||||||
parameters:
|
|
||||||
- name: id
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema: { type: integer }
|
|
||||||
description: 'Job ID'
|
|
||||||
requestBody:
|
|
||||||
description: 'Array of tags to add'
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/Tag'
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 'Job resource'
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Job'
|
|
||||||
404:
|
|
||||||
description: 'Job or tag does not exist'
|
|
||||||
400:
|
|
||||||
description: 'Bad request'
|
|
||||||
'/api/jobs/start_job/':
|
|
||||||
post:
|
|
||||||
operationId: 'startJob'
|
|
||||||
summary: 'Add a newly started job'
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Job'
|
|
||||||
responses:
|
|
||||||
201:
|
|
||||||
description: 'Job successfully'
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: integer
|
|
||||||
description: 'The database ID assigned to this job'
|
|
||||||
400:
|
|
||||||
description: 'Bad request'
|
|
||||||
422:
|
|
||||||
description: 'The combination of jobId, clusterId and startTime does already exist'
|
|
||||||
'/api/jobs/stop_job/':
|
|
||||||
post:
|
|
||||||
operationId: stopJobViaJobID
|
|
||||||
summary: 'Mark a job as stopped. Which job to stop is specified by the request body.'
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
required: [jobId, cluster, stopTime, jobState]
|
|
||||||
properties:
|
|
||||||
jobId: { type: integer }
|
|
||||||
cluster: { type: string }
|
|
||||||
startTime: { type: integer }
|
|
||||||
stopTime: { type: integer }
|
|
||||||
jobState:
|
|
||||||
type: string
|
|
||||||
enum: ["running", "completed", "failed", "canceled", "stopped", "timeout"]
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 'Job resource'
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Job'
|
|
||||||
400:
|
|
||||||
description: 'Bad request'
|
|
||||||
404:
|
|
||||||
description: 'Resource not found'
|
|
||||||
'/api/jobs/stop_job/{id}':
|
|
||||||
post:
|
|
||||||
operationId: 'stopJobViaDBID'
|
|
||||||
summary: 'Mark a job as stopped.'
|
|
||||||
parameters:
|
|
||||||
- name: id
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema: { type: integer }
|
|
||||||
description: 'Database ID (Resource Identifier)'
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
required: [stopTime, jobState]
|
|
||||||
properties:
|
|
||||||
stopTime: { type: integer }
|
|
||||||
jobState:
|
|
||||||
type: string
|
|
||||||
enum: ["running", "completed", "failed", "canceled", "stopped", "timeout"]
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 'Job resource'
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Job'
|
|
||||||
400:
|
|
||||||
description: 'Bad request'
|
|
||||||
404:
|
|
||||||
description: 'Resource not found'
|
|
||||||
'/api/jobs/import/':
|
|
||||||
post:
|
|
||||||
operationId: 'importJob'
|
|
||||||
summary: 'Imports a job and its metric data'
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
meta:
|
|
||||||
$ref: https://raw.githubusercontent.com/ClusterCockpit/cc-specifications/master/schema/json/job-meta.schema.json
|
|
||||||
data:
|
|
||||||
$ref: https://raw.githubusercontent.com/ClusterCockpit/cc-specifications/master/schema/json/job-data.schema.json
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: 'Import successful'
|
|
||||||
400:
|
|
||||||
description: 'Bad request'
|
|
||||||
422:
|
|
||||||
description: 'Unprocessable Entity'
|
|
||||||
components:
|
|
||||||
schemas:
|
|
||||||
Tag:
|
|
||||||
description: 'A job tag'
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
id:
|
|
||||||
type: string
|
|
||||||
description: 'Database ID'
|
|
||||||
type:
|
|
||||||
type: string
|
|
||||||
description: 'Tag type'
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
description: 'Tag name'
|
|
||||||
Job:
|
|
||||||
$ref: https://raw.githubusercontent.com/ClusterCockpit/cc-specifications/master/schema/json/job-meta.schema.json
|
|
||||||
securitySchemes:
|
|
||||||
bearerAuth:
|
|
||||||
type: http
|
|
||||||
scheme: bearer
|
|
||||||
bearerFormat: JWT
|
|
||||||
security:
|
|
||||||
- bearerAuth: [] # Applies `bearerAuth` globally
|
|
@ -447,68 +447,7 @@ func (api *RestApi) stopJobById(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity checks
|
api.checkAndHandleStopJob(rw, job, req)
|
||||||
if job == nil || job.StartTime.Unix() >= req.StopTime || job.State != schema.JobStateRunning {
|
|
||||||
handleError(errors.New("stopTime must be larger than startTime and only running jobs can be stopped"), http.StatusBadRequest, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if req.State != "" && !req.State.Valid() {
|
|
||||||
handleError(fmt.Errorf("invalid job state: %#v", req.State), http.StatusBadRequest, rw)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
req.State = schema.JobStateCompleted
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark job as stopped in the database (update state and duration)
|
|
||||||
job.Duration = int32(req.StopTime - job.StartTime.Unix())
|
|
||||||
job.State = req.State
|
|
||||||
if err := api.JobRepository.Stop(job.ID, job.Duration, job.State, job.MonitoringStatus); err != nil {
|
|
||||||
handleError(fmt.Errorf("marking job as stopped failed: %w", err), http.StatusInternalServerError, rw)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("archiving job... (dbid: %d): cluster=%s, jobId=%d, user=%s, startTime=%s", job.ID, job.Cluster, job.JobID, job.User, job.StartTime)
|
|
||||||
|
|
||||||
// Send a response (with status OK). This means that erros that happen from here on forward
|
|
||||||
// can *NOT* be communicated to the client. If reading from a MetricDataRepository or
|
|
||||||
// writing to the filesystem fails, the client will not know.
|
|
||||||
rw.Header().Add("Content-Type", "application/json")
|
|
||||||
rw.WriteHeader(http.StatusOK)
|
|
||||||
json.NewEncoder(rw).Encode(job)
|
|
||||||
|
|
||||||
// Monitoring is disabled...
|
|
||||||
if job.MonitoringStatus == schema.MonitoringStatusDisabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to start a new goroutine as this functions needs to return
|
|
||||||
// for the response to be flushed to the client.
|
|
||||||
api.OngoingArchivings.Add(1) // So that a shutdown does not interrupt this goroutine.
|
|
||||||
go func() {
|
|
||||||
defer api.OngoingArchivings.Done()
|
|
||||||
|
|
||||||
if _, err := api.JobRepository.FetchMetadata(job); err != nil {
|
|
||||||
log.Errorf("archiving job (dbid: %d) failed: %s", job.ID, err.Error())
|
|
||||||
api.JobRepository.UpdateMonitoringStatus(job.ID, schema.MonitoringStatusArchivingFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// metricdata.ArchiveJob will fetch all the data from a MetricDataRepository and create meta.json/data.json files
|
|
||||||
jobMeta, err := metricdata.ArchiveJob(job, context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("archiving job (dbid: %d) failed: %s", job.ID, err.Error())
|
|
||||||
api.JobRepository.UpdateMonitoringStatus(job.ID, schema.MonitoringStatusArchivingFailed)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the jobs database entry one last time:
|
|
||||||
if err := api.JobRepository.Archive(job.ID, schema.MonitoringStatusArchivingSuccessful, jobMeta.Statistics); err != nil {
|
|
||||||
log.Errorf("archiving job (dbid: %d) failed: %s", job.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("archiving job (dbid: %d) successful", job.ID)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopJobByRequest godoc
|
// stopJobByRequest godoc
|
||||||
@ -554,11 +493,17 @@ func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api.checkAndHandleStopJob(rw, job, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobApiRequest) {
|
||||||
|
|
||||||
// Sanity checks
|
// Sanity checks
|
||||||
if job == nil || job.StartTime.Unix() >= req.StopTime || job.State != schema.JobStateRunning {
|
if job == nil || job.StartTime.Unix() >= req.StopTime || job.State != schema.JobStateRunning {
|
||||||
handleError(errors.New("stopTime must be larger than startTime and only running jobs can be stopped"), http.StatusBadRequest, rw)
|
handleError(errors.New("stopTime must be larger than startTime and only running jobs can be stopped"), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.State != "" && !req.State.Valid() {
|
if req.State != "" && !req.State.Valid() {
|
||||||
handleError(fmt.Errorf("invalid job state: %#v", req.State), http.StatusBadRequest, rw)
|
handleError(fmt.Errorf("invalid job state: %#v", req.State), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user