From 263363a2ba86b6ce6c637944d782fa5f8ceccb97 Mon Sep 17 00:00:00 2001 From: dailz Date: Wed, 8 Apr 2026 18:30:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Diag/Ping/Licenses?= =?UTF-8?q?/Reconfigure=20=E7=B1=BB=E5=9E=8B=E5=92=8C=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 包含 StatsMsg(含调度和回填统计)、ControllerPing、License 等类型。实现 4 个服务:DiagService.GetDiag、PingService.Ping、LicensesService.GetLicenses、ReconfigureService.Reconfigure。 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- internal/slurm/slurm_diag.go | 67 +++++++ internal/slurm/slurm_diag_test.go | 162 ++++++++++++++++ internal/slurm/types_diag.go | 149 ++++++++++++++ internal/slurm/types_diag_test.go | 309 ++++++++++++++++++++++++++++++ 4 files changed, 687 insertions(+) create mode 100644 internal/slurm/slurm_diag.go create mode 100644 internal/slurm/slurm_diag_test.go create mode 100644 internal/slurm/types_diag.go create mode 100644 internal/slurm/types_diag_test.go diff --git a/internal/slurm/slurm_diag.go b/internal/slurm/slurm_diag.go new file mode 100644 index 0000000..220a47a --- /dev/null +++ b/internal/slurm/slurm_diag.go @@ -0,0 +1,67 @@ +package slurm + +import "context" + +// GetDiag returns slurm diagnostics information. +func (s *DiagService) GetDiag(ctx context.Context) (*OpenapiDiagResp, *Response, error) { + path := "slurm/v0.0.40/diag" + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + var result OpenapiDiagResp + resp, err := s.client.Do(ctx, req, &result) + if err != nil { + return nil, resp, err + } + return &result, resp, nil +} + +// Ping returns slurm controller ping information. +func (s *PingService) Ping(ctx context.Context) (*OpenapiPingArrayResp, *Response, error) { + path := "slurm/v0.0.40/ping" + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + var result OpenapiPingArrayResp + resp, err := s.client.Do(ctx, req, &result) + if err != nil { + return nil, resp, err + } + return &result, resp, nil +} + +// GetLicenses returns slurm license information. +func (s *LicensesService) GetLicenses(ctx context.Context) (*OpenapiLicensesResp, *Response, error) { + path := "slurm/v0.0.40/licenses" + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + var result OpenapiLicensesResp + resp, err := s.client.Do(ctx, req, &result) + if err != nil { + return nil, resp, err + } + return &result, resp, nil +} + +// Reconfigure requests slurm reconfigure. +func (s *ReconfigureService) Reconfigure(ctx context.Context) (*OpenapiResp, *Response, error) { + path := "slurm/v0.0.40/reconfigure" + req, err := s.client.NewRequest("GET", path, nil) + if err != nil { + return nil, nil, err + } + + var result OpenapiResp + resp, err := s.client.Do(ctx, req, &result) + if err != nil { + return nil, resp, err + } + return &result, resp, nil +} diff --git a/internal/slurm/slurm_diag_test.go b/internal/slurm/slurm_diag_test.go new file mode 100644 index 0000000..f78678a --- /dev/null +++ b/internal/slurm/slurm_diag_test.go @@ -0,0 +1,162 @@ +package slurm + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestDiagService_GetDiag(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/slurm/v0.0.40/diag", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"statistics": {"parts": 1}, "meta": {"plugin": {"type": "openapi/slurmctld"}}}`) + }) + server := httptest.NewServer(mux) + defer server.Close() + + client, _ := NewClient(server.URL, nil) + resp, _, err := client.Diag.GetDiag(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp == nil { + t.Fatal("expected non-nil response") + } + if resp.Statistics == nil { + t.Fatal("expected non-nil statistics") + } +} + +func TestDiagService_GetDiag_Error(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/slurm/v0.0.40/diag", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, `{"errors": [{"error": "internal error"}]}`) + }) + server := httptest.NewServer(mux) + defer server.Close() + + client, _ := NewClient(server.URL, nil) + _, _, err := client.Diag.GetDiag(context.Background()) + if err == nil { + t.Fatal("expected error for 500 response") + } + if !strings.Contains(err.Error(), "500") { + t.Errorf("expected error to contain 500, got %v", err) + } +} + +func TestPingService_Ping(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/slurm/v0.0.40/ping", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"pings": [{"hostname": "slurmctl", "pinged": "UP", "latency": 100, "mode": "primary"}]}`) + }) + server := httptest.NewServer(mux) + defer server.Close() + + client, _ := NewClient(server.URL, nil) + resp, _, err := client.Ping.Ping(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp == nil { + t.Fatal("expected non-nil response") + } + if len(resp.Pings) != 1 { + t.Errorf("expected 1 ping, got %d", len(resp.Pings)) + } +} + +func TestPingService_Ping_Error(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/slurm/v0.0.40/ping", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + fmt.Fprint(w, `{"errors": [{"error": "service unavailable"}]}`) + }) + server := httptest.NewServer(mux) + defer server.Close() + + client, _ := NewClient(server.URL, nil) + _, _, err := client.Ping.Ping(context.Background()) + if err == nil { + t.Fatal("expected error for 503 response") + } +} + +func TestLicensesService_GetLicenses(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/slurm/v0.0.40/licenses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{"licenses": [{"LicenseName": "matlab", "Total": 10, "Used": 3, "Free": 7, "Remote": false}]}`) + }) + server := httptest.NewServer(mux) + defer server.Close() + + client, _ := NewClient(server.URL, nil) + resp, _, err := client.Licenses.GetLicenses(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp == nil { + t.Fatal("expected non-nil response") + } + if len(resp.Licenses) != 1 { + t.Errorf("expected 1 license, got %d", len(resp.Licenses)) + } +} + +func TestLicensesService_GetLicenses_Error(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/slurm/v0.0.40/licenses", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, `{"errors": [{"error": "internal error"}]}`) + }) + server := httptest.NewServer(mux) + defer server.Close() + + client, _ := NewClient(server.URL, nil) + _, _, err := client.Licenses.GetLicenses(context.Background()) + if err == nil { + t.Fatal("expected error for 500 response") + } +} + +func TestReconfigureService_Reconfigure(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/slurm/v0.0.40/reconfigure", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{}`) + }) + server := httptest.NewServer(mux) + defer server.Close() + + client, _ := NewClient(server.URL, nil) + resp, _, err := client.Reconfigure.Reconfigure(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if resp == nil { + t.Fatal("expected non-nil response") + } +} + +func TestReconfigureService_Reconfigure_Error(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/slurm/v0.0.40/reconfigure", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, `{"errors": [{"error": "internal error"}]}`) + }) + server := httptest.NewServer(mux) + defer server.Close() + + client, _ := NewClient(server.URL, nil) + _, _, err := client.Reconfigure.Reconfigure(context.Background()) + if err == nil { + t.Fatal("expected error for 500 response") + } +} diff --git a/internal/slurm/types_diag.go b/internal/slurm/types_diag.go new file mode 100644 index 0000000..5b382cc --- /dev/null +++ b/internal/slurm/types_diag.go @@ -0,0 +1,149 @@ +package slurm + +// ScheduleExitFields represents schedule exit fields (v0.0.40_schedule_exit_fields). +type ScheduleExitFields struct { + EndJobQueue *int32 `json:"end_job_queue,omitempty"` + DefaultQueueDepth *int32 `json:"default_queue_depth,omitempty"` + MaxJobStart *int32 `json:"max_job_start,omitempty"` + MaxRpcCnt *int32 `json:"max_rpc_cnt,omitempty"` + MaxSchedTime *int32 `json:"max_sched_time,omitempty"` + Licenses *int32 `json:"licenses,omitempty"` +} + +// BfExitFields represents backfill exit fields (v0.0.40_bf_exit_fields). +type BfExitFields struct { + EndJobQueue *int32 `json:"end_job_queue,omitempty"` + BfMaxJobStart *int32 `json:"bf_max_job_start,omitempty"` + BfMaxJobTest *int32 `json:"bf_max_job_test,omitempty"` + BfMaxTime *int32 `json:"bf_max_time,omitempty"` + BfNodeSpaceSize *int32 `json:"bf_node_space_size,omitempty"` + StateChanged *int32 `json:"state_changed,omitempty"` +} + +// StatsMsgRpcsByTypeItem represents a single RPC stats by message type entry. +type StatsMsgRpcsByTypeItem struct { + MessageType *string `json:"message_type,omitempty"` + TypeID *int32 `json:"type_id,omitempty"` + Count *int64 `json:"count,omitempty"` + AverageTime *int64 `json:"average_time,omitempty"` + TotalTime *int64 `json:"total_time,omitempty"` +} + +// StatsMsgRpcsByType represents RPC stats by message type (v0.0.40_stats_msg_rpcs_by_type). +type StatsMsgRpcsByType []StatsMsgRpcsByTypeItem + +// StatsMsgRpcsByUserItem represents a single RPC stats by user entry. +type StatsMsgRpcsByUserItem struct { + User *string `json:"user,omitempty"` + UserID *int32 `json:"user_id,omitempty"` + Count *int64 `json:"count,omitempty"` + AverageTime *int64 `json:"average_time,omitempty"` + TotalTime *int64 `json:"total_time,omitempty"` +} + +// StatsMsgRpcsByUser represents RPC stats by user (v0.0.40_stats_msg_rpcs_by_user). +type StatsMsgRpcsByUser []StatsMsgRpcsByUserItem + +// StatsMsg represents Slurm statistics message (v0.0.40_stats_msg). +type StatsMsg struct { + PartsPacked *int32 `json:"parts_packed,omitempty"` + ReqTime *Uint64NoVal `json:"req_time,omitempty"` + ReqTimeStart *Uint64NoVal `json:"req_time_start,omitempty"` + ServerThreadCount *int32 `json:"server_thread_count,omitempty"` + AgentQueueSize *int32 `json:"agent_queue_size,omitempty"` + AgentCount *int32 `json:"agent_count,omitempty"` + AgentThreadCount *int32 `json:"agent_thread_count,omitempty"` + DbdAgentQueueSize *int32 `json:"dbd_agent_queue_size,omitempty"` + GettimeofdayLatency *int32 `json:"gettimeofday_latency,omitempty"` + ScheduleCycleMax *int32 `json:"schedule_cycle_max,omitempty"` + ScheduleCycleLast *int32 `json:"schedule_cycle_last,omitempty"` + ScheduleCycleTotal *int32 `json:"schedule_cycle_total,omitempty"` + ScheduleCycleMean *int64 `json:"schedule_cycle_mean,omitempty"` + ScheduleCycleMeanDepth *int64 `json:"schedule_cycle_mean_depth,omitempty"` + ScheduleCyclePerMinute *int64 `json:"schedule_cycle_per_minute,omitempty"` + ScheduleQueueLength *int32 `json:"schedule_queue_length,omitempty"` + ScheduleExit *ScheduleExitFields `json:"schedule_exit,omitempty"` + JobsSubmitted *int32 `json:"jobs_submitted,omitempty"` + JobsStarted *int32 `json:"jobs_started,omitempty"` + JobsCompleted *int32 `json:"jobs_completed,omitempty"` + JobsCanceled *int32 `json:"jobs_canceled,omitempty"` + JobsFailed *int32 `json:"jobs_failed,omitempty"` + JobsPending *int32 `json:"jobs_pending,omitempty"` + JobsRunning *int32 `json:"jobs_running,omitempty"` + JobStatesTs *Uint64NoVal `json:"job_states_ts,omitempty"` + BfBackfilledJobs *int32 `json:"bf_backfilled_jobs,omitempty"` + BfLastBackfilledJobs *int32 `json:"bf_last_backfilled_jobs,omitempty"` + BfBackfilledHetJobs *int32 `json:"bf_backfilled_het_jobs,omitempty"` + BfCycleCounter *int32 `json:"bf_cycle_counter,omitempty"` + BfCycleMean *int64 `json:"bf_cycle_mean,omitempty"` + BfDepthMean *int64 `json:"bf_depth_mean,omitempty"` + BfDepthMeanTry *int64 `json:"bf_depth_mean_try,omitempty"` + BfCycleSum *int64 `json:"bf_cycle_sum,omitempty"` + BfCycleLast *int32 `json:"bf_cycle_last,omitempty"` + BfLastDepth *int32 `json:"bf_last_depth,omitempty"` + BfLastDepthTry *int32 `json:"bf_last_depth_try,omitempty"` + BfDepthSum *int32 `json:"bf_depth_sum,omitempty"` + BfDepthTrySum *int32 `json:"bf_depth_try_sum,omitempty"` + BfQueueLen *int32 `json:"bf_queue_len,omitempty"` + BfQueueLenMean *int64 `json:"bf_queue_len_mean,omitempty"` + BfQueueLenSum *int32 `json:"bf_queue_len_sum,omitempty"` + BfTableSize *int32 `json:"bf_table_size,omitempty"` + BfTableSizeMean *int64 `json:"bf_table_size_mean,omitempty"` + BfWhenLastCycle *Uint64NoVal `json:"bf_when_last_cycle,omitempty"` + BfActive *bool `json:"bf_active,omitempty"` + BfExit *BfExitFields `json:"bf_exit,omitempty"` + RpcsByMessageType *StatsMsgRpcsByType `json:"rpcs_by_message_type,omitempty"` + RpcsByUser *StatsMsgRpcsByUser `json:"rpcs_by_user,omitempty"` +} + +// ControllerPing represents a controller ping result (v0.0.40_controller_ping). +type ControllerPing struct { + Hostname *string `json:"hostname,omitempty"` + Pinged *string `json:"pinged,omitempty"` + Latency *int64 `json:"latency,omitempty"` + Mode *string `json:"mode,omitempty"` +} + +// ControllerPingArray represents an array of controller ping results (v0.0.40_controller_ping_array). +type ControllerPingArray []ControllerPing + +// License represents Slurm license information (v0.0.40_license). +type License struct { + LicenseName *string `json:"LicenseName,omitempty"` + Total *int32 `json:"Total,omitempty"` + Used *int32 `json:"Used,omitempty"` + Free *int32 `json:"Free,omitempty"` + Remote *bool `json:"Remote,omitempty"` + Reserved *int32 `json:"Reserved,omitempty"` + LastConsumed *int32 `json:"LastConsumed,omitempty"` + LastDeficit *int32 `json:"LastDeficit,omitempty"` + LastUpdate *int64 `json:"LastUpdate,omitempty"` +} + +// Licenses represents a list of licenses (v0.0.40_licenses). +type Licenses []License + +// OpenapiDiagResp represents the diag API response (v0.0.40_openapi_diag_resp). +type OpenapiDiagResp struct { + Statistics *StatsMsg `json:"statistics,omitempty"` + Meta *OpenapiMeta `json:"meta,omitempty"` + Errors OpenapiErrors `json:"errors,omitempty"` + Warnings OpenapiWarnings `json:"warnings,omitempty"` +} + +// OpenapiPingArrayResp represents the ping API response (v0.0.40_openapi_ping_array_resp). +type OpenapiPingArrayResp struct { + Pings ControllerPingArray `json:"pings,omitempty"` + Meta *OpenapiMeta `json:"meta,omitempty"` + Errors OpenapiErrors `json:"errors,omitempty"` + Warnings OpenapiWarnings `json:"warnings,omitempty"` +} + +// OpenapiLicensesResp represents the licenses API response (v0.0.40_openapi_licenses_resp). +type OpenapiLicensesResp struct { + Licenses Licenses `json:"licenses,omitempty"` + LastUpdate *Uint64NoVal `json:"last_update,omitempty"` + Meta *OpenapiMeta `json:"meta,omitempty"` + Errors OpenapiErrors `json:"errors,omitempty"` + Warnings OpenapiWarnings `json:"warnings,omitempty"` +} diff --git a/internal/slurm/types_diag_test.go b/internal/slurm/types_diag_test.go new file mode 100644 index 0000000..3e94b99 --- /dev/null +++ b/internal/slurm/types_diag_test.go @@ -0,0 +1,309 @@ +package slurm + +import ( + "encoding/json" + "testing" +) + +func TestStatsMsgRoundTrip(t *testing.T) { + orig := StatsMsg{ + PartsPacked: Ptr(int32(1)), + ReqTime: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1700000000))}, + ReqTimeStart: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1699999999))}, + ServerThreadCount: Ptr(int32(4)), + AgentQueueSize: Ptr(int32(0)), + AgentCount: Ptr(int32(10)), + AgentThreadCount: Ptr(int32(4)), + DbdAgentQueueSize: Ptr(int32(0)), + GettimeofdayLatency: Ptr(int32(1)), + ScheduleCycleMax: Ptr(int32(100)), + ScheduleCycleLast: Ptr(int32(5)), + ScheduleCycleTotal: Ptr(int32(500)), + ScheduleCycleMean: Ptr(int64(10)), + ScheduleCycleMeanDepth: Ptr(int64(3)), + ScheduleCyclePerMinute: Ptr(int64(60)), + ScheduleQueueLength: Ptr(int32(42)), + ScheduleExit: &ScheduleExitFields{ + EndJobQueue: Ptr(int32(1)), + DefaultQueueDepth: Ptr(int32(100)), + MaxJobStart: Ptr(int32(0)), + MaxRpcCnt: Ptr(int32(0)), + MaxSchedTime: Ptr(int32(0)), + Licenses: Ptr(int32(0)), + }, + JobsSubmitted: Ptr(int32(1000)), + JobsStarted: Ptr(int32(900)), + JobsCompleted: Ptr(int32(850)), + JobsCanceled: Ptr(int32(20)), + JobsFailed: Ptr(int32(5)), + JobsPending: Ptr(int32(30)), + JobsRunning: Ptr(int32(45)), + JobStatesTs: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1700000000))}, + BfBackfilledJobs: Ptr(int32(200)), + BfLastBackfilledJobs: Ptr(int32(5)), + BfBackfilledHetJobs: Ptr(int32(0)), + BfCycleCounter: Ptr(int32(500)), + BfCycleMean: Ptr(int64(2)), + BfDepthMean: Ptr(int64(10)), + BfDepthMeanTry: Ptr(int64(8)), + BfCycleSum: Ptr(int64(1000)), + BfCycleLast: Ptr(int32(3)), + BfLastDepth: Ptr(int32(15)), + BfLastDepthTry: Ptr(int32(12)), + BfDepthSum: Ptr(int32(5000)), + BfDepthTrySum: Ptr(int32(4000)), + BfQueueLen: Ptr(int32(30)), + BfQueueLenMean: Ptr(int64(25)), + BfQueueLenSum: Ptr(int32(15000)), + BfTableSize: Ptr(int32(500)), + BfTableSizeMean: Ptr(int64(450)), + BfWhenLastCycle: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1700000050))}, + BfActive: Ptr(true), + BfExit: &BfExitFields{ + EndJobQueue: Ptr(int32(0)), + BfMaxJobStart: Ptr(int32(0)), + BfMaxJobTest: Ptr(int32(0)), + BfMaxTime: Ptr(int32(0)), + BfNodeSpaceSize: Ptr(int32(0)), + StateChanged: Ptr(int32(0)), + }, + RpcsByMessageType: &StatsMsgRpcsByType{ + {MessageType: Ptr("REQUEST_JOB_INFO"), TypeID: Ptr(int32(2001)), Count: Ptr(int64(500)), AverageTime: Ptr(int64(10)), TotalTime: Ptr(int64(5000))}, + {MessageType: Ptr("REQUEST_NODE_INFO"), TypeID: Ptr(int32(2002)), Count: Ptr(int64(300)), AverageTime: Ptr(int64(5)), TotalTime: Ptr(int64(1500))}, + }, + RpcsByUser: &StatsMsgRpcsByUser{ + {User: Ptr("root"), UserID: Ptr(int32(0)), Count: Ptr(int64(100)), AverageTime: Ptr(int64(3)), TotalTime: Ptr(int64(300))}, + {User: Ptr("alice"), UserID: Ptr(int32(1001)), Count: Ptr(int64(50)), AverageTime: Ptr(int64(7)), TotalTime: Ptr(int64(350))}, + }, + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var decoded StatsMsg + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if *decoded.PartsPacked != *orig.PartsPacked { + t.Errorf("PartsPacked: got %d, want %d", *decoded.PartsPacked, *orig.PartsPacked) + } + if *decoded.ScheduleExit.DefaultQueueDepth != *orig.ScheduleExit.DefaultQueueDepth { + t.Errorf("ScheduleExit.DefaultQueueDepth: got %d, want %d", *decoded.ScheduleExit.DefaultQueueDepth, *orig.ScheduleExit.DefaultQueueDepth) + } + if *decoded.BfActive != *orig.BfActive { + t.Errorf("BfActive: got %v, want %v", *decoded.BfActive, *orig.BfActive) + } + if len(*decoded.RpcsByMessageType) != len(*orig.RpcsByMessageType) { + t.Errorf("RpcsByMessageType length: got %d, want %d", len(*decoded.RpcsByMessageType), len(*orig.RpcsByMessageType)) + } + if len(*decoded.RpcsByUser) != len(*orig.RpcsByUser) { + t.Errorf("RpcsByUser length: got %d, want %d", len(*decoded.RpcsByUser), len(*orig.RpcsByUser)) + } + if *(*decoded.RpcsByMessageType)[0].MessageType != *(*orig.RpcsByMessageType)[0].MessageType { + t.Errorf("RpcsByMessageType[0].MessageType: got %s, want %s", *(*decoded.RpcsByMessageType)[0].MessageType, *(*orig.RpcsByMessageType)[0].MessageType) + } +} + +func TestScheduleExitFieldsRoundTrip(t *testing.T) { + orig := ScheduleExitFields{ + EndJobQueue: Ptr(int32(1)), + DefaultQueueDepth: Ptr(int32(100)), + MaxJobStart: Ptr(int32(50)), + MaxRpcCnt: Ptr(int32(20)), + MaxSchedTime: Ptr(int32(30)), + Licenses: Ptr(int32(5)), + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var decoded ScheduleExitFields + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if *decoded.EndJobQueue != *orig.EndJobQueue { + t.Errorf("EndJobQueue: got %d, want %d", *decoded.EndJobQueue, *orig.EndJobQueue) + } + if *decoded.Licenses != *orig.Licenses { + t.Errorf("Licenses: got %d, want %d", *decoded.Licenses, *orig.Licenses) + } +} + +func TestBfExitFieldsRoundTrip(t *testing.T) { + orig := BfExitFields{ + EndJobQueue: Ptr(int32(1)), + BfMaxJobStart: Ptr(int32(10)), + BfMaxJobTest: Ptr(int32(100)), + BfMaxTime: Ptr(int32(60)), + BfNodeSpaceSize: Ptr(int32(500)), + StateChanged: Ptr(int32(3)), + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var decoded BfExitFields + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if *decoded.BfMaxJobTest != *orig.BfMaxJobTest { + t.Errorf("BfMaxJobTest: got %d, want %d", *decoded.BfMaxJobTest, *orig.BfMaxJobTest) + } + if *decoded.StateChanged != *orig.StateChanged { + t.Errorf("StateChanged: got %d, want %d", *decoded.StateChanged, *orig.StateChanged) + } +} + +func TestControllerPingRoundTrip(t *testing.T) { + orig := ControllerPing{ + Hostname: Ptr("controller1"), + Pinged: Ptr("UP"), + Latency: Ptr(int64(42)), + Mode: Ptr("primary"), + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var decoded ControllerPing + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if *decoded.Hostname != *orig.Hostname { + t.Errorf("Hostname: got %s, want %s", *decoded.Hostname, *orig.Hostname) + } + if *decoded.Latency != *orig.Latency { + t.Errorf("Latency: got %d, want %d", *decoded.Latency, *orig.Latency) + } +} + +func TestLicenseRoundTrip(t *testing.T) { + orig := License{ + LicenseName: Ptr("matlab"), + Total: Ptr(int32(100)), + Used: Ptr(int32(50)), + Free: Ptr(int32(50)), + Remote: Ptr(false), + Reserved: Ptr(int32(10)), + LastConsumed: Ptr(int32(45)), + LastDeficit: Ptr(int32(0)), + LastUpdate: Ptr(int64(1700000000)), + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var decoded License + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if *decoded.LicenseName != *orig.LicenseName { + t.Errorf("LicenseName: got %s, want %s", *decoded.LicenseName, *orig.LicenseName) + } + if *decoded.Total != *orig.Total { + t.Errorf("Total: got %d, want %d", *decoded.Total, *orig.Total) + } + if *decoded.Remote != *orig.Remote { + t.Errorf("Remote: got %v, want %v", *decoded.Remote, *orig.Remote) + } +} + +func TestOpenapiDiagRespRoundTrip(t *testing.T) { + orig := OpenapiDiagResp{ + Statistics: &StatsMsg{ + PartsPacked: Ptr(int32(1)), + ServerThreadCount: Ptr(int32(4)), + AgentCount: Ptr(int32(10)), + JobsRunning: Ptr(int32(45)), + BfActive: Ptr(true), + }, + Meta: &OpenapiMeta{ + Slurm: &MetaSlurm{ + Version: &MetaSlurmVersion{Major: Ptr("24"), Minor: Ptr("05"), Micro: Ptr("5")}, + Release: Ptr("24.05.5"), + }, + }, + Errors: OpenapiErrors{}, + Warnings: OpenapiWarnings{}, + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var decoded OpenapiDiagResp + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if *decoded.Statistics.PartsPacked != *orig.Statistics.PartsPacked { + t.Errorf("Statistics.PartsPacked: got %d, want %d", *decoded.Statistics.PartsPacked, *orig.Statistics.PartsPacked) + } + if *decoded.Meta.Slurm.Release != *orig.Meta.Slurm.Release { + t.Errorf("Meta.Slurm.Release: got %s, want %s", *decoded.Meta.Slurm.Release, *orig.Meta.Slurm.Release) + } +} + +func TestOpenapiPingArrayRespRoundTrip(t *testing.T) { + orig := OpenapiPingArrayResp{ + Pings: ControllerPingArray{ + {Hostname: Ptr("ctrl1"), Pinged: Ptr("UP"), Latency: Ptr(int64(10))}, + {Hostname: Ptr("ctrl2"), Pinged: Ptr("DOWN"), Latency: Ptr(int64(9999))}, + }, + Meta: &OpenapiMeta{ + Slurm: &MetaSlurm{ + Version: &MetaSlurmVersion{Major: Ptr("24"), Minor: Ptr("05"), Micro: Ptr("5")}, + }, + }, + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var decoded OpenapiPingArrayResp + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if len(decoded.Pings) != len(orig.Pings) { + t.Fatalf("Pings length: got %d, want %d", len(decoded.Pings), len(orig.Pings)) + } + if *decoded.Pings[0].Hostname != *orig.Pings[0].Hostname { + t.Errorf("Pings[0].Hostname: got %s, want %s", *decoded.Pings[0].Hostname, *orig.Pings[0].Hostname) + } + if *decoded.Pings[1].Pinged != *orig.Pings[1].Pinged { + t.Errorf("Pings[1].Pinged: got %s, want %s", *decoded.Pings[1].Pinged, *orig.Pings[1].Pinged) + } +} + +func TestOpenapiLicensesRespRoundTrip(t *testing.T) { + orig := OpenapiLicensesResp{ + Licenses: Licenses{ + {LicenseName: Ptr("matlab"), Total: Ptr(int32(100)), Used: Ptr(int32(50))}, + {LicenseName: Ptr("ansys"), Total: Ptr(int32(10)), Used: Ptr(int32(3))}, + }, + LastUpdate: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1700000000))}, + Meta: &OpenapiMeta{ + Slurm: &MetaSlurm{ + Version: &MetaSlurmVersion{Major: Ptr("24"), Minor: Ptr("05"), Micro: Ptr("5")}, + }, + }, + } + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("marshal: %v", err) + } + var decoded OpenapiLicensesResp + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if len(decoded.Licenses) != len(orig.Licenses) { + t.Fatalf("Licenses length: got %d, want %d", len(decoded.Licenses), len(orig.Licenses)) + } + if *decoded.Licenses[0].LicenseName != *orig.Licenses[0].LicenseName { + t.Errorf("Licenses[0].LicenseName: got %s, want %s", *decoded.Licenses[0].LicenseName, *orig.Licenses[0].LicenseName) + } + if *decoded.Licenses[1].Used != *orig.Licenses[1].Used { + t.Errorf("Licenses[1].Used: got %d, want %d", *decoded.Licenses[1].Used, *orig.Licenses[1].Used) + } + if *decoded.LastUpdate.Number != *orig.LastUpdate.Number { + t.Errorf("LastUpdate.Number: got %d, want %d", *decoded.LastUpdate.Number, *orig.LastUpdate.Number) + } +}