feat(cmd/mockserver): add standalone mock server for frontend development
Reuse MockSlurm + MockMinIO + TestEnv wiring pattern to create a standalone binary that serves all API endpoints with in-memory SQLite and seed data. - internal/mockserver/server.go: assembly logic (New/Close/Run), option pattern, 4 accessors for seed data injection - cmd/mockserver/main.go: CLI flags (--port, --seed, MOCK_PORT), 6 seed jobs in all 5 states + 2 seed applications, signal handling Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
131
cmd/mockserver/main.go
Normal file
131
cmd/mockserver/main.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gcy_hpc_server/internal/mockserver"
|
||||
"gcy_hpc_server/internal/model"
|
||||
"gcy_hpc_server/internal/slurm"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defaultPort := 8080
|
||||
if v := os.Getenv("MOCK_PORT"); v != "" {
|
||||
if p, err := strconv.Atoi(v); err == nil {
|
||||
defaultPort = p
|
||||
}
|
||||
}
|
||||
|
||||
port := flag.Int("port", defaultPort, "listen port")
|
||||
seed := flag.Bool("seed", true, "inject seed data")
|
||||
flag.Parse()
|
||||
|
||||
srv, err := mockserver.New(mockserver.WithPort(*port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create server: %v", err)
|
||||
}
|
||||
|
||||
if *seed {
|
||||
injectSeedData(srv)
|
||||
}
|
||||
|
||||
if err := srv.Run(); err != nil {
|
||||
log.Fatalf("server error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func injectSeedData(srv *mockserver.Server) {
|
||||
client := srv.MockSlurmHTTPClient()
|
||||
baseURL := srv.MockSlurmURL()
|
||||
|
||||
type jobDef struct {
|
||||
name string
|
||||
partition string
|
||||
states []string
|
||||
}
|
||||
|
||||
jobs := []jobDef{
|
||||
{"gpu-training-job", "gpu", nil},
|
||||
{"data-processing", "normal", []string{"RUNNING"}},
|
||||
{"benchmark-test", "normal", []string{"RUNNING"}},
|
||||
{"model-evaluation", "gpu", []string{"RUNNING", "COMPLETED"}},
|
||||
{"failed-script", "normal", []string{"RUNNING", "FAILED"}},
|
||||
{"cancelled-job", "gpu", []string{"RUNNING", "CANCELLED"}},
|
||||
}
|
||||
|
||||
mockSlurm := srv.MockSlurm()
|
||||
for _, j := range jobs {
|
||||
id, err := submitJob(client, baseURL, j.name, j.partition)
|
||||
if err != nil {
|
||||
log.Printf("warning: failed to submit job %q: %v", j.name, err)
|
||||
continue
|
||||
}
|
||||
for _, state := range j.states {
|
||||
mockSlurm.SetJobState(id, state)
|
||||
}
|
||||
}
|
||||
|
||||
appSvc := srv.ApplicationService()
|
||||
ctx := context.Background()
|
||||
|
||||
apps := []model.CreateApplicationRequest{
|
||||
{
|
||||
Name: "图像分类训练",
|
||||
Description: "基于ResNet的图像分类模型训练",
|
||||
ScriptTemplate: "#!/bin/bash\n#SBATCH --job-name={{.Name}}\necho 'Running {{.Name}}'",
|
||||
},
|
||||
{
|
||||
Name: "数据处理流水线",
|
||||
Description: "ETL数据处理脚本",
|
||||
ScriptTemplate: "#!/bin/bash\necho 'Processing data'",
|
||||
},
|
||||
}
|
||||
for _, app := range apps {
|
||||
if _, err := appSvc.CreateApplication(ctx, &app); err != nil {
|
||||
log.Printf("warning: failed to create application %q: %v", app.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Seeded 6 jobs (1 PENDING, 2 RUNNING, 1 COMPLETED, 1 FAILED, 1 CANCELLED) and 2 applications")
|
||||
}
|
||||
|
||||
func submitJob(client *http.Client, baseURL, name, partition string) (int32, error) {
|
||||
body := fmt.Sprintf(
|
||||
`{"script":"#!/bin/bash\necho %s","job":{"name":"%s","partition":"%s"}}`,
|
||||
name, name, partition,
|
||||
)
|
||||
req, err := http.NewRequest("POST", baseURL+"/slurm/v0.0.40/job/submit", strings.NewReader(body))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("do request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return 0, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, b)
|
||||
}
|
||||
|
||||
var result slurm.OpenapiJobSubmitResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return 0, fmt.Errorf("decode response: %w", err)
|
||||
}
|
||||
if result.JobID == nil {
|
||||
return 0, fmt.Errorf("no job_id in response")
|
||||
}
|
||||
return *result.JobID, nil
|
||||
}
|
||||
Reference in New Issue
Block a user