mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-10-25 14:55:06 +02: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:
		
							
								
								
									
										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 | ||||
| 	} | ||||
|  | ||||
| 	// Sanity checks | ||||
| 	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) | ||||
| 	}() | ||||
| 	api.checkAndHandleStopJob(rw, job, req) | ||||
| } | ||||
|  | ||||
| // stopJobByRequest godoc | ||||
| @@ -554,11 +493,17 @@ func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	api.checkAndHandleStopJob(rw, job, req) | ||||
| } | ||||
|  | ||||
| func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobApiRequest) { | ||||
|  | ||||
| 	// Sanity checks | ||||
| 	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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user