diff --git a/hpc_server_openapi.json b/hpc_server_openapi.json new file mode 100644 index 0000000..d3f300e --- /dev/null +++ b/hpc_server_openapi.json @@ -0,0 +1,1691 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "GCY HPC Server API", + "description": "Slurm HPC cluster management API for job submission, cluster monitoring, and job template management.", + "version": "0.1.0" + }, + "servers": [ + { + "url": "/api/v1", + "description": "API v1" + } + ], + "tags": [ + { + "name": "Jobs", + "description": "Job submission, listing, cancellation, and history" + }, + { + "name": "Cluster", + "description": "Cluster node, partition, and diagnostic information" + }, + { + "name": "Templates", + "description": "Job template CRUD operations" + } + ], + "paths": { + "/jobs/submit": { + "post": { + "tags": ["Jobs"], + "summary": "Submit a new job", + "description": "Submits a Slurm job with the specified script and optional parameters.", + "operationId": "submitJob", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubmitJobRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Job submitted successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/JobResponse" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Invalid request body or missing required fields", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + }, + "examples": { + "invalid_body": { + "value": { + "success": false, + "error": "invalid request body" + } + }, + "missing_script": { + "value": { + "success": false, + "error": "script is required" + } + } + } + } + } + }, + "502": { + "description": "Slurm backend error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/jobs": { + "get": { + "tags": ["Jobs"], + "summary": "List all jobs with pagination", + "description": "Retrieves a paginated list of all current jobs.", + "operationId": "getJobs", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number (starts from 1)", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + } + }, + { + "name": "page_size", + "in": "query", + "description": "Number of items per page", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "default": 20 + } + } + ], + "responses": { + "200": { + "description": "Paginated list of jobs", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/JobListResponse" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Invalid query parameters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/jobs/history": { + "get": { + "tags": ["Jobs"], + "summary": "Get job history", + "description": "Retrieves paginated job history with optional filters.", + "operationId": "getJobHistory", + "parameters": [ + { + "name": "users", + "in": "query", + "description": "Filter by username(s)", + "schema": { + "type": "string" + } + }, + { + "name": "start_time", + "in": "query", + "description": "Filter by start time (Unix timestamp or date string)", + "schema": { + "type": "string" + } + }, + { + "name": "end_time", + "in": "query", + "description": "Filter by end time (Unix timestamp or date string)", + "schema": { + "type": "string" + } + }, + { + "name": "account", + "in": "query", + "description": "Filter by account", + "schema": { + "type": "string" + } + }, + { + "name": "partition", + "in": "query", + "description": "Filter by partition", + "schema": { + "type": "string" + } + }, + { + "name": "state", + "in": "query", + "description": "Filter by job state", + "schema": { + "type": "string" + } + }, + { + "name": "job_name", + "in": "query", + "description": "Filter by job name", + "schema": { + "type": "string" + } + }, + { + "name": "submit_time", + "in": "query", + "description": "Filter by submit time (Unix timestamp)", + "schema": { + "type": "string" + } + }, + { + "name": "cluster", + "in": "query", + "description": "Filter by cluster name", + "schema": { + "type": "string" + } + }, + { + "name": "qos", + "in": "query", + "description": "Filter by QOS policy", + "schema": { + "type": "string" + } + }, + { + "name": "constraints", + "in": "query", + "description": "Filter by node constraints", + "schema": { + "type": "string" + } + }, + { + "name": "exit_code", + "in": "query", + "description": "Filter by exit code", + "schema": { + "type": "string" + } + }, + { + "name": "node", + "in": "query", + "description": "Filter by allocated node", + "schema": { + "type": "string" + } + }, + { + "name": "reservation", + "in": "query", + "description": "Filter by reservation name", + "schema": { + "type": "string" + } + }, + { + "name": "groups", + "in": "query", + "description": "Filter by user groups", + "schema": { + "type": "string" + } + }, + { + "name": "wckey", + "in": "query", + "description": "Filter by WCKey (Workload Characterization Key)", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number (default: 1)", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + } + }, + { + "name": "page_size", + "in": "query", + "description": "Items per page (default: 20)", + "schema": { + "type": "integer", + "minimum": 1, + "default": 20 + } + } + ], + "responses": { + "200": { + "description": "Paginated job history", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/JobListResponse" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Invalid query parameters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/jobs/{id}": { + "get": { + "tags": ["Jobs"], + "summary": "Get job by ID", + "description": "Retrieves details for a specific job. If the job is not found in the active queue, automatically queries SlurmDBD history.", + "operationId": "getJob", + "parameters": [ + { + "$ref": "#/components/parameters/JobId" + } + ], + "responses": { + "200": { + "description": "Job details", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/JobResponse" + } + } + } + ] + } + } + } + }, + "404": { + "description": "Job not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + }, + "delete": { + "tags": ["Jobs"], + "summary": "Cancel a job", + "description": "Cancels a running or pending job by ID.", + "operationId": "cancelJob", + "parameters": [ + { + "$ref": "#/components/parameters/JobId" + } + ], + "responses": { + "200": { + "description": "Job cancelled successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "job cancelled" + } + } + } + } + } + ] + } + } + } + }, + "502": { + "description": "Slurm backend error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/nodes": { + "get": { + "tags": ["Cluster"], + "summary": "List all nodes", + "description": "Retrieves a list of all cluster nodes.", + "operationId": "getNodes", + "responses": { + "200": { + "description": "List of nodes", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NodeResponse" + } + } + } + } + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/nodes/{name}": { + "get": { + "tags": ["Cluster"], + "summary": "Get node by name", + "description": "Retrieves details for a specific cluster node.", + "operationId": "getNode", + "parameters": [ + { + "$ref": "#/components/parameters/NodeName" + } + ], + "responses": { + "200": { + "description": "Node details", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/NodeResponse" + } + } + } + ] + } + } + } + }, + "404": { + "description": "Node not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/partitions": { + "get": { + "tags": ["Cluster"], + "summary": "List all partitions", + "description": "Retrieves a list of all cluster partitions.", + "operationId": "getPartitions", + "responses": { + "200": { + "description": "List of partitions", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PartitionResponse" + } + } + } + } + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/partitions/{name}": { + "get": { + "tags": ["Cluster"], + "summary": "Get partition by name", + "description": "Retrieves details for a specific cluster partition.", + "operationId": "getPartition", + "parameters": [ + { + "$ref": "#/components/parameters/PartitionName" + } + ], + "responses": { + "200": { + "description": "Partition details", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/PartitionResponse" + } + } + } + ] + } + } + } + }, + "404": { + "description": "Partition not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/diag": { + "get": { + "tags": ["Cluster"], + "summary": "Get cluster diagnostics", + "description": "Retrieves diagnostic information about the Slurm cluster.", + "operationId": "getDiag", + "responses": { + "200": { + "description": "Cluster diagnostics", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "Slurm cluster diagnostic data" + } + } + } + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/templates": { + "get": { + "tags": ["Templates"], + "summary": "List all templates", + "description": "Retrieves a paginated list of job templates.", + "operationId": "listTemplates", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number (default: 1)", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + } + }, + { + "name": "page_size", + "in": "query", + "description": "Items per page (default: 20)", + "schema": { + "type": "integer", + "minimum": 1, + "default": 20 + } + } + ], + "responses": { + "200": { + "description": "Paginated list of templates", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/TemplateListResponse" + } + } + } + ] + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + }, + "post": { + "tags": ["Templates"], + "summary": "Create a template", + "description": "Creates a new job template with the provided script and parameters.", + "operationId": "createTemplate", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTemplateRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Template created successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "The ID of the newly created template" + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Invalid request body or missing required fields", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + }, + "examples": { + "invalid_body": { + "value": { + "success": false, + "error": "invalid request body" + } + }, + "missing_fields": { + "value": { + "success": false, + "error": "name and script are required" + } + } + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + }, + "/templates/{id}": { + "get": { + "tags": ["Templates"], + "summary": "Get template by ID", + "description": "Retrieves a specific job template.", + "operationId": "getTemplate", + "parameters": [ + { + "$ref": "#/components/parameters/TemplateId" + } + ], + "responses": { + "200": { + "description": "Template details", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/JobTemplate" + } + } + } + ] + } + } + } + }, + "400": { + "description": "Invalid template ID", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "404": { + "description": "Template not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + }, + "put": { + "tags": ["Templates"], + "summary": "Update a template", + "description": "Updates an existing job template.", + "operationId": "updateTemplate", + "parameters": [ + { + "$ref": "#/components/parameters/TemplateId" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTemplateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Template updated successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "template updated" + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Invalid template ID or request body", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "404": { + "description": "Template not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + }, + "delete": { + "tags": ["Templates"], + "summary": "Delete a template", + "description": "Deletes a job template by ID.", + "operationId": "deleteTemplate", + "parameters": [ + { + "$ref": "#/components/parameters/TemplateId" + } + ], + "responses": { + "200": { + "description": "Template deleted successfully", + "content": { + "application/json": { + "schema": { + "allOf": [ + { "$ref": "#/components/schemas/ApiResponseSuccess" }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "template deleted" + } + } + } + } + } + ] + } + } + } + }, + "400": { + "description": "Invalid template ID", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseError" + } + } + } + } + } + } + } + }, + "components": { + "parameters": { + "JobId": { + "name": "id", + "in": "path", + "required": true, + "description": "Job ID", + "schema": { + "type": "string" + } + }, + "NodeName": { + "name": "name", + "in": "path", + "required": true, + "description": "Node name", + "schema": { + "type": "string" + } + }, + "PartitionName": { + "name": "name", + "in": "path", + "required": true, + "description": "Partition name", + "schema": { + "type": "string" + } + }, + "TemplateId": { + "name": "id", + "in": "path", + "required": true, + "description": "Template ID", + "schema": { + "type": "integer", + "format": "int64" + } + } + }, + "schemas": { + "ApiResponseSuccess": { + "type": "object", + "required": ["success"], + "properties": { + "success": { + "type": "boolean", + "enum": [true] + }, + "data": { + "description": "Response payload" + } + } + }, + "ApiResponseError": { + "type": "object", + "required": ["success", "error"], + "properties": { + "success": { + "type": "boolean", + "enum": [false] + }, + "error": { + "type": "string", + "description": "Error message" + } + } + }, + "SubmitJobRequest": { + "type": "object", + "required": ["script"], + "properties": { + "script": { + "type": "string", + "description": "Job script content" + }, + "partition": { + "type": "string", + "description": "Target partition" + }, + "qos": { + "type": "string", + "description": "Quality of Service" + }, + "cpus": { + "type": "integer", + "format": "int32", + "description": "Number of CPUs required" + }, + "memory": { + "type": "string", + "description": "Memory requirement (e.g. \"4G\")" + }, + "time_limit": { + "type": "string", + "description": "Time limit (e.g. \"1:00:00\")" + }, + "job_name": { + "type": "string", + "description": "Job name" + }, + "environment": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Environment variables" + } + } + }, + "JobResponse": { + "type": "object", + "properties": { + "job_id": { + "type": "integer", + "format": "int32", + "description": "Slurm job ID" + }, + "name": { + "type": "string", + "description": "Job name" + }, + "job_state": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Current job state(s) (e.g. [\"RUNNING\"], [\"PENDING\",\"REQUEUED\"])" + }, + "state_reason": { + "type": "string", + "description": "Reason for job pending or failure" + }, + "partition": { + "type": "string", + "description": "Assigned partition" + }, + "qos": { + "type": "string", + "description": "Quality of Service policy" + }, + "priority": { + "type": "integer", + "format": "int32", + "description": "Job priority", + "nullable": true + }, + "time_limit": { + "type": "string", + "description": "Time limit in minutes (\"UNLIMITED\" for unlimited)" + }, + "account": { + "type": "string", + "description": "Billing account" + }, + "user": { + "type": "string", + "description": "Submitting user" + }, + "cluster": { + "type": "string", + "description": "Cluster name" + }, + "cpus": { + "type": "integer", + "format": "int32", + "description": "Allocated/requested CPU cores", + "nullable": true + }, + "tasks": { + "type": "integer", + "format": "int32", + "description": "Number of tasks", + "nullable": true + }, + "node_count": { + "type": "integer", + "format": "int32", + "description": "Number of allocated nodes", + "nullable": true + }, + "nodes": { + "type": "string", + "description": "Allocated node list" + }, + "batch_host": { + "type": "string", + "description": "Batch host node" + }, + "submit_time": { + "type": "integer", + "format": "int64", + "description": "Submit time (Unix timestamp)", + "nullable": true + }, + "start_time": { + "type": "integer", + "format": "int64", + "description": "Start time (Unix timestamp)", + "nullable": true + }, + "end_time": { + "type": "integer", + "format": "int64", + "description": "End time (Unix timestamp)", + "nullable": true + }, + "exit_code": { + "type": "integer", + "format": "int32", + "description": "Exit code (null if not finished)", + "nullable": true + }, + "standard_output": { + "type": "string", + "description": "Stdout file path" + }, + "standard_error": { + "type": "string", + "description": "Stderr file path" + }, + "standard_input": { + "type": "string", + "description": "Stdin file path" + }, + "working_directory": { + "type": "string", + "description": "Working directory" + }, + "command": { + "type": "string", + "description": "Executed command" + }, + "array_job_id": { + "type": "integer", + "format": "int32", + "description": "Parent job ID for array jobs", + "nullable": true + }, + "array_task_id": { + "type": "integer", + "format": "int32", + "description": "Task ID within array job", + "nullable": true + } + } + }, + "JobListResponse": { + "type": "object", + "properties": { + "jobs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JobResponse" + }, + "description": "List of jobs" + }, + "total": { + "type": "integer", + "description": "Total number of matching jobs" + }, + "page": { + "type": "integer", + "description": "Current page number (starts at 1)" + }, + "page_size": { + "type": "integer", + "description": "Items per page" + } + } + }, + "NodeResponse": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Node hostname" + }, + "state": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Node state(s) (e.g. [\"IDLE\"], [\"ALLOCATED\",\"COMPLETING\"])" + }, + "reason": { + "type": "string", + "description": "Reason for DOWN/DRAIN state" + }, + "reason_set_by_user": { + "type": "string", + "description": "User who set the reason" + }, + "cpus": { + "type": "integer", + "format": "int32", + "description": "Total CPU cores" + }, + "alloc_cpus": { + "type": "integer", + "format": "int32", + "description": "Allocated CPU cores", + "nullable": true + }, + "cores": { + "type": "integer", + "format": "int32", + "description": "Physical cores", + "nullable": true + }, + "sockets": { + "type": "integer", + "format": "int32", + "description": "CPU sockets", + "nullable": true + }, + "threads": { + "type": "integer", + "format": "int32", + "description": "Threads per core", + "nullable": true + }, + "cpu_load": { + "type": "integer", + "format": "int32", + "description": "CPU load (kernel nice value * 100)", + "nullable": true + }, + "real_memory": { + "type": "integer", + "format": "int64", + "description": "Total physical memory (MiB)" + }, + "alloc_memory": { + "type": "integer", + "format": "int64", + "description": "Allocated memory (MiB)" + }, + "free_mem": { + "type": "integer", + "format": "int64", + "description": "Free memory (MiB)", + "nullable": true + }, + "architecture": { + "type": "string", + "description": "System architecture (e.g. x86_64)" + }, + "operating_system": { + "type": "string", + "description": "Operating system version" + }, + "gres": { + "type": "string", + "description": "Available generic resources (e.g. \"gpu:4\")" + }, + "gres_used": { + "type": "string", + "description": "Used generic resources (e.g. \"gpu:2\")" + }, + "address": { + "type": "string", + "description": "Node address (IP)" + }, + "hostname": { + "type": "string", + "description": "Node hostname (may differ from name)" + }, + "weight": { + "type": "integer", + "format": "int32", + "description": "Scheduling weight", + "nullable": true + }, + "features": { + "type": "string", + "description": "Node feature tags (modifiable)" + }, + "active_features": { + "type": "string", + "description": "Active feature tags (read-only)" + } + } + }, + "PartitionResponse": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Partition name" + }, + "state": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Partition state(s) (e.g. [\"UP\"], [\"DOWN\",\"DRAIN\"])" + }, + "default": { + "type": "boolean", + "description": "Whether this is the default partition" + }, + "nodes": { + "type": "string", + "description": "Node range string" + }, + "total_nodes": { + "type": "integer", + "format": "int32", + "description": "Total nodes in partition" + }, + "total_cpus": { + "type": "integer", + "format": "int32", + "description": "Total CPUs in partition" + }, + "max_cpus_per_node": { + "type": "integer", + "format": "int32", + "description": "Max CPUs per node", + "nullable": true + }, + "max_time": { + "type": "string", + "description": "Maximum time limit (\"UNLIMITED\" for unlimited)" + }, + "max_nodes": { + "type": "integer", + "format": "int32", + "description": "Max nodes per job", + "nullable": true + }, + "min_nodes": { + "type": "integer", + "format": "int32", + "description": "Min nodes per job", + "nullable": true + }, + "default_time": { + "type": "string", + "description": "Default time limit" + }, + "grace_time": { + "type": "integer", + "format": "int32", + "description": "Grace time after preemption (seconds)", + "nullable": true + }, + "priority": { + "type": "integer", + "format": "int32", + "description": "Job priority factor within partition", + "nullable": true + }, + "qos_allowed": { + "type": "string", + "description": "Allowed QOS list" + }, + "qos_deny": { + "type": "string", + "description": "Denied QOS list" + }, + "qos_assigned": { + "type": "string", + "description": "Default assigned QOS" + }, + "accounts_allowed": { + "type": "string", + "description": "Allowed accounts list" + }, + "accounts_deny": { + "type": "string", + "description": "Denied accounts list" + } + } + }, + "JobTemplate": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "Template ID" + }, + "name": { + "type": "string", + "description": "Template name" + }, + "description": { + "type": "string", + "description": "Template description" + }, + "script": { + "type": "string", + "description": "Job script content" + }, + "partition": { + "type": "string", + "description": "Default partition" + }, + "qos": { + "type": "string", + "description": "Default QOS" + }, + "cpus": { + "type": "integer", + "description": "Default CPU count" + }, + "memory": { + "type": "string", + "description": "Default memory" + }, + "time_limit": { + "type": "string", + "description": "Default time limit" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Creation timestamp" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Last update timestamp" + } + } + }, + "CreateTemplateRequest": { + "type": "object", + "required": ["name", "script"], + "properties": { + "name": { + "type": "string", + "description": "Template name" + }, + "description": { + "type": "string", + "description": "Template description" + }, + "script": { + "type": "string", + "description": "Job script content" + }, + "partition": { + "type": "string", + "description": "Default partition" + }, + "qos": { + "type": "string", + "description": "Default QOS" + }, + "cpus": { + "type": "integer", + "description": "Default CPU count" + }, + "memory": { + "type": "string", + "description": "Default memory" + }, + "time_limit": { + "type": "string", + "description": "Default time limit" + } + } + }, + "UpdateTemplateRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Template name" + }, + "description": { + "type": "string", + "description": "Template description" + }, + "script": { + "type": "string", + "description": "Job script content" + }, + "partition": { + "type": "string", + "description": "Default partition" + }, + "qos": { + "type": "string", + "description": "Default QOS" + }, + "cpus": { + "type": "integer", + "description": "Default CPU count" + }, + "memory": { + "type": "string", + "description": "Default memory" + }, + "time_limit": { + "type": "string", + "description": "Default time limit" + } + } + }, + "TemplateListResponse": { + "type": "object", + "properties": { + "templates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JobTemplate" + } + }, + "total": { + "type": "integer", + "description": "Total number of templates" + }, + "page": { + "type": "integer", + "description": "Current page number" + }, + "page_size": { + "type": "integer", + "description": "Items per page" + } + } + } + } + } +}