feat(model): add Task model, DTOs, and status constants for task submission system
This commit is contained in:
93
internal/model/task.go
Normal file
93
internal/model/task.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Task status constants.
|
||||||
|
const (
|
||||||
|
TaskStatusSubmitted = "submitted"
|
||||||
|
TaskStatusPreparing = "preparing"
|
||||||
|
TaskStatusDownloading = "downloading"
|
||||||
|
TaskStatusReady = "ready"
|
||||||
|
TaskStatusQueued = "queued"
|
||||||
|
TaskStatusRunning = "running"
|
||||||
|
TaskStatusCompleted = "completed"
|
||||||
|
TaskStatusFailed = "failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Task step constants for step-level retry tracking.
|
||||||
|
const (
|
||||||
|
TaskStepPreparing = "preparing"
|
||||||
|
TaskStepDownloading = "downloading"
|
||||||
|
TaskStepSubmitting = "submitting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Task represents an HPC task submitted through the application framework.
|
||||||
|
type Task struct {
|
||||||
|
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||||
|
TaskName string `gorm:"size:255" json:"task_name"`
|
||||||
|
AppID int64 `json:"app_id"`
|
||||||
|
AppName string `gorm:"size:255" json:"app_name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CurrentStep string `json:"current_step"`
|
||||||
|
RetryCount int `json:"retry_count"`
|
||||||
|
Values json.RawMessage `gorm:"type:text" json:"values,omitempty"`
|
||||||
|
InputFileIDs json.RawMessage `json:"input_file_ids" gorm:"column:input_file_ids;type:text"`
|
||||||
|
Script string `json:"script,omitempty"`
|
||||||
|
SlurmJobID *int32 `json:"slurm_job_id,omitempty"`
|
||||||
|
WorkDir string `json:"work_dir,omitempty"`
|
||||||
|
Partition string `json:"partition,omitempty"`
|
||||||
|
ErrorMessage string `json:"error_message,omitempty"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
SubmittedAt time.Time `json:"submitted_at"`
|
||||||
|
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||||
|
FinishedAt *time.Time `json:"finished_at,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Task) TableName() string {
|
||||||
|
return "hpc_tasks"
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTaskRequest is the DTO for creating a new task.
|
||||||
|
type CreateTaskRequest struct {
|
||||||
|
AppID int64 `json:"app_id" binding:"required"`
|
||||||
|
TaskName string `json:"task_name"`
|
||||||
|
Values map[string]string `json:"values"`
|
||||||
|
InputFileIDs []int64 `json:"file_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskResponse is the DTO returned in API responses.
|
||||||
|
type TaskResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
TaskName string `json:"task_name"`
|
||||||
|
AppID int64 `json:"app_id"`
|
||||||
|
AppName string `json:"app_name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CurrentStep string `json:"current_step"`
|
||||||
|
RetryCount int `json:"retry_count"`
|
||||||
|
SlurmJobID *int32 `json:"slurm_job_id"`
|
||||||
|
WorkDir string `json:"work_dir"`
|
||||||
|
ErrorMessage string `json:"error_message"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskListResponse is the paginated response for listing tasks.
|
||||||
|
type TaskListResponse struct {
|
||||||
|
Items []TaskResponse `json:"items"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskListQuery contains query parameters for listing tasks.
|
||||||
|
type TaskListQuery struct {
|
||||||
|
Page int `form:"page" json:"page,omitempty"`
|
||||||
|
PageSize int `form:"page_size" json:"page_size,omitempty"`
|
||||||
|
Status string `form:"status" json:"status,omitempty"`
|
||||||
|
}
|
||||||
104
internal/model/task_test.go
Normal file
104
internal/model/task_test.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTask_TableName(t *testing.T) {
|
||||||
|
task := Task{}
|
||||||
|
if got := task.TableName(); got != "hpc_tasks" {
|
||||||
|
t.Errorf("Task.TableName() = %q, want %q", got, "hpc_tasks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTask_JSONRoundTrip(t *testing.T) {
|
||||||
|
now := time.Now().UTC().Truncate(time.Second)
|
||||||
|
jobID := int32(42)
|
||||||
|
|
||||||
|
task := Task{
|
||||||
|
ID: 1,
|
||||||
|
TaskName: "test task",
|
||||||
|
AppID: 10,
|
||||||
|
AppName: "GROMACS",
|
||||||
|
Status: TaskStatusRunning,
|
||||||
|
CurrentStep: TaskStepSubmitting,
|
||||||
|
RetryCount: 1,
|
||||||
|
Values: json.RawMessage(`{"np":"4"}`),
|
||||||
|
InputFileIDs: json.RawMessage(`[1,2,3]`),
|
||||||
|
Script: "#!/bin/bash",
|
||||||
|
SlurmJobID: &jobID,
|
||||||
|
WorkDir: "/data/work",
|
||||||
|
Partition: "gpu",
|
||||||
|
ErrorMessage: "",
|
||||||
|
UserID: "user1",
|
||||||
|
SubmittedAt: now,
|
||||||
|
StartedAt: &now,
|
||||||
|
FinishedAt: nil,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(task)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal task: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var got Task
|
||||||
|
if err := json.Unmarshal(data, &got); err != nil {
|
||||||
|
t.Fatalf("unmarshal task: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got.ID != task.ID {
|
||||||
|
t.Errorf("ID = %d, want %d", got.ID, task.ID)
|
||||||
|
}
|
||||||
|
if got.TaskName != task.TaskName {
|
||||||
|
t.Errorf("TaskName = %q, want %q", got.TaskName, task.TaskName)
|
||||||
|
}
|
||||||
|
if got.Status != task.Status {
|
||||||
|
t.Errorf("Status = %q, want %q", got.Status, task.Status)
|
||||||
|
}
|
||||||
|
if got.CurrentStep != task.CurrentStep {
|
||||||
|
t.Errorf("CurrentStep = %q, want %q", got.CurrentStep, task.CurrentStep)
|
||||||
|
}
|
||||||
|
if got.RetryCount != task.RetryCount {
|
||||||
|
t.Errorf("RetryCount = %d, want %d", got.RetryCount, task.RetryCount)
|
||||||
|
}
|
||||||
|
if got.SlurmJobID == nil || *got.SlurmJobID != jobID {
|
||||||
|
t.Errorf("SlurmJobID = %v, want %d", got.SlurmJobID, jobID)
|
||||||
|
}
|
||||||
|
if got.UserID != task.UserID {
|
||||||
|
t.Errorf("UserID = %q, want %q", got.UserID, task.UserID)
|
||||||
|
}
|
||||||
|
if string(got.Values) != string(task.Values) {
|
||||||
|
t.Errorf("Values = %s, want %s", got.Values, task.Values)
|
||||||
|
}
|
||||||
|
if string(got.InputFileIDs) != string(task.InputFileIDs) {
|
||||||
|
t.Errorf("InputFileIDs = %s, want %s", got.InputFileIDs, task.InputFileIDs)
|
||||||
|
}
|
||||||
|
if got.FinishedAt != nil {
|
||||||
|
t.Errorf("FinishedAt = %v, want nil", got.FinishedAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateTaskRequest_JSONBinding(t *testing.T) {
|
||||||
|
payload := `{"app_id":5,"task_name":"my task","values":{"np":"8"},"file_ids":[10,20]}`
|
||||||
|
var req CreateTaskRequest
|
||||||
|
if err := json.Unmarshal([]byte(payload), &req); err != nil {
|
||||||
|
t.Fatalf("unmarshal CreateTaskRequest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.AppID != 5 {
|
||||||
|
t.Errorf("AppID = %d, want 5", req.AppID)
|
||||||
|
}
|
||||||
|
if req.TaskName != "my task" {
|
||||||
|
t.Errorf("TaskName = %q, want %q", req.TaskName, "my task")
|
||||||
|
}
|
||||||
|
if v, ok := req.Values["np"]; !ok || v != "8" {
|
||||||
|
t.Errorf("Values[\"np\"] = %q, want %q", v, "8")
|
||||||
|
}
|
||||||
|
if len(req.InputFileIDs) != 2 || req.InputFileIDs[0] != 10 || req.InputFileIDs[1] != 20 {
|
||||||
|
t.Errorf("InputFileIDs = %v, want [10 20]", req.InputFileIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user