feat: 添加 Node 领域类型和 NodesService

包含 Node、UpdateNodeMsg、AcctGatherEnergy、ExtSensorsData、PowerMgmtData 等类型。NodesService 提供 GetNodes、GetNode、PostNode、DeleteNode 4 个方法,并定义 NodeFlag* 常量。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
dailz
2026-04-08 18:29:26 +08:00
parent 2c84930983
commit f9234b2167
4 changed files with 706 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
package slurm
import (
"context"
"fmt"
"net/url"
)
// Node query flags for GetNodesOptions.Flags and GetNodeOptions.Flags.
const (
NodeFlagAll = "ALL"
NodeFlagDetail = "DETAIL"
NodeFlagMixed = "MIXED"
NodeFlagLocal = "LOCAL"
NodeFlagSibling = "SIBLING"
NodeFlagFederation = "FEDERATION"
NodeFlagFuture = "FUTURE"
)
// GetNodesOptions specifies optional parameters for GetNodes.
type GetNodesOptions struct {
UpdateTime *string `url:"update_time,omitempty"`
Flags *string `url:"flags,omitempty"` // Use NodeFlag* constants (e.g. NodeFlagDetail)
}
// GetNodeOptions specifies optional parameters for GetNode.
type GetNodeOptions struct {
UpdateTime *string `url:"update_time,omitempty"`
Flags *string `url:"flags,omitempty"` // Use NodeFlag* constants (e.g. NodeFlagDetail)
}
// GetNodes lists all nodes.
func (s *NodesService) GetNodes(ctx context.Context, opts *GetNodesOptions) (*OpenapiNodesResp, *Response, error) {
path := "slurm/v0.0.40/nodes"
req, err := s.client.NewRequest("GET", path, nil)
if err != nil {
return nil, nil, err
}
if opts != nil {
u, parseErr := url.Parse(req.URL.String())
if parseErr != nil {
return nil, nil, parseErr
}
q := u.Query()
if opts.UpdateTime != nil {
q.Set("update_time", *opts.UpdateTime)
}
if opts.Flags != nil {
q.Set("flags", *opts.Flags)
}
u.RawQuery = q.Encode()
req.URL = u
}
var result OpenapiNodesResp
resp, err := s.client.Do(ctx, req, &result)
if err != nil {
return nil, resp, err
}
return &result, resp, nil
}
// GetNode gets a single node by name.
func (s *NodesService) GetNode(ctx context.Context, nodeName string, opts *GetNodeOptions) (*OpenapiNodesResp, *Response, error) {
path := fmt.Sprintf("slurm/v0.0.40/node/%s", nodeName)
req, err := s.client.NewRequest("GET", path, nil)
if err != nil {
return nil, nil, err
}
if opts != nil {
u, parseErr := url.Parse(req.URL.String())
if parseErr != nil {
return nil, nil, parseErr
}
q := u.Query()
if opts.UpdateTime != nil {
q.Set("update_time", *opts.UpdateTime)
}
if opts.Flags != nil {
q.Set("flags", *opts.Flags)
}
u.RawQuery = q.Encode()
req.URL = u
}
var result OpenapiNodesResp
resp, err := s.client.Do(ctx, req, &result)
if err != nil {
return nil, resp, err
}
return &result, resp, nil
}
// PostNode updates a node.
func (s *NodesService) PostNode(ctx context.Context, nodeName string, update *UpdateNodeMsg) (*OpenapiResp, *Response, error) {
path := fmt.Sprintf("slurm/v0.0.40/node/%s", nodeName)
req, err := s.client.NewRequest("POST", path, update)
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
}
// DeleteNode deletes a node.
func (s *NodesService) DeleteNode(ctx context.Context, nodeName string) (*OpenapiResp, *Response, error) {
path := fmt.Sprintf("slurm/v0.0.40/node/%s", nodeName)
req, err := s.client.NewRequest("DELETE", 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
}

View File

@@ -0,0 +1,163 @@
package slurm
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestNodesService_GetNodes(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/nodes", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"nodes": []}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
resp, _, err := client.Nodes.GetNodes(context.Background(), nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
}
func TestNodesService_GetNodes_WithOptions(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/nodes", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
q := r.URL.Query()
if q.Get("update_time") != "12345" {
t.Errorf("expected update_time=12345, got %s", q.Get("update_time"))
}
if q.Get("flags") != NodeFlagDetail {
t.Errorf("expected flags=%s, got %s", NodeFlagDetail, q.Get("flags"))
}
fmt.Fprint(w, `{"nodes": []}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
opts := &GetNodesOptions{
UpdateTime: Ptr("12345"),
Flags: Ptr(NodeFlagDetail),
}
resp, _, err := client.Nodes.GetNodes(context.Background(), opts)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
}
func TestNodesService_GetNode(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/node/node1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"nodes": [{"name": "node1"}]}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
resp, _, err := client.Nodes.GetNode(context.Background(), "node1", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
if resp.Nodes == nil || len(*resp.Nodes) != 1 {
t.Fatalf("expected 1 node, got %v", resp.Nodes)
}
if resp.Nodes == nil || (*resp.Nodes)[0].Name == nil || *(*resp.Nodes)[0].Name != "node1" {
t.Errorf("expected name=node1, got %v", resp.Nodes)
}
}
func TestNodesService_PostNode(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/node/node1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
t.Errorf("expected Content-Type application/json, got %s", ct)
}
fmt.Fprint(w, `{}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
update := &UpdateNodeMsg{
Comment: Ptr("updated comment"),
}
resp, _, err := client.Nodes.PostNode(context.Background(), "node1", update)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
}
func TestNodesService_DeleteNode(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/node/node1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
fmt.Fprint(w, `{}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
resp, _, err := client.Nodes.DeleteNode(context.Background(), "node1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
}
func TestNodesService_GetNode_Error(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/node/nonexistent", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, `{"errors": [{"error": "node not found"}]}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
_, _, err := client.Nodes.GetNode(context.Background(), "nonexistent", nil)
if err == nil {
t.Fatal("expected error for 404 response")
}
if !strings.Contains(err.Error(), "404") {
t.Errorf("expected error to contain 404, got %v", err)
}
}
func TestNodesService_GetNodes_Error(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/nodes", 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.Nodes.GetNodes(context.Background(), nil)
if err == nil {
t.Fatal("expected error for 500 response")
}
}

View File

@@ -0,0 +1,121 @@
package slurm
// AcctGatherEnergy represents energy accounting data (v0.0.40_acct_gather_energy).
type AcctGatherEnergy struct {
AverageWatts *int32 `json:"average_watts,omitempty"`
BaseConsumedEnergy *int64 `json:"base_consumed_energy,omitempty"`
ConsumedEnergy *int64 `json:"consumed_energy,omitempty"`
CurrentWatts *Uint32NoVal `json:"current_watts,omitempty"`
PreviousConsumedEnergy *int64 `json:"previous_consumed_energy,omitempty"`
LastCollected *int64 `json:"last_collected,omitempty"`
}
// ExtSensorsData represents external sensor data (v0.0.40_ext_sensors_data).
type ExtSensorsData struct {
ConsumedEnergy *Uint64NoVal `json:"consumed_energy,omitempty"`
Temperature *Uint32NoVal `json:"temperature,omitempty"`
EnergyUpdateTime *int64 `json:"energy_update_time,omitempty"`
CurrentWatts *int32 `json:"current_watts,omitempty"`
}
// PowerMgmtData represents power management data (v0.0.40_power_mgmt_data).
type PowerMgmtData struct {
MaximumWatts *Uint32NoVal `json:"maximum_watts,omitempty"`
CurrentWatts *int32 `json:"current_watts,omitempty"`
TotalEnergy *int64 `json:"total_energy,omitempty"`
NewMaximumWatts *int32 `json:"new_maximum_watts,omitempty"`
PeakWatts *int32 `json:"peak_watts,omitempty"`
LowestWatts *int32 `json:"lowest_watts,omitempty"`
NewJobTime *Uint64NoVal `json:"new_job_time,omitempty"`
State *int32 `json:"state,omitempty"`
TimeStartDay *int64 `json:"time_start_day,omitempty"`
}
// Node represents a Slurm compute node (v0.0.40_node).
type Node struct {
Architecture *string `json:"architecture,omitempty"`
BurstbufferNetworkAddress *string `json:"burstbuffer_network_address,omitempty"`
Boards *int32 `json:"boards,omitempty"`
BootTime *Uint64NoVal `json:"boot_time,omitempty"`
ClusterName *string `json:"cluster_name,omitempty"`
Cores *int32 `json:"cores,omitempty"`
SpecializedCores *int32 `json:"specialized_cores,omitempty"`
CpuBinding *int32 `json:"cpu_binding,omitempty"`
CpuLoad *int32 `json:"cpu_load,omitempty"`
FreeMem *Uint64NoVal `json:"free_mem,omitempty"`
Cpus *int32 `json:"cpus,omitempty"`
EffectiveCpus *int32 `json:"effective_cpus,omitempty"`
SpecializedCpus *string `json:"specialized_cpus,omitempty"`
Energy *AcctGatherEnergy `json:"energy,omitempty"`
ExternalSensors *ExtSensorsData `json:"external_sensors,omitempty"`
Extra *string `json:"extra,omitempty"`
Power *PowerMgmtData `json:"power,omitempty"`
Features *CSVString `json:"features,omitempty"`
ActiveFeatures *CSVString `json:"active_features,omitempty"`
Gres *string `json:"gres,omitempty"`
GresDrained *string `json:"gres_drained,omitempty"`
GresUsed *string `json:"gres_used,omitempty"`
InstanceID *string `json:"instance_id,omitempty"`
InstanceType *string `json:"instance_type,omitempty"`
LastBusy *Uint64NoVal `json:"last_busy,omitempty"`
McsLabel *string `json:"mcs_label,omitempty"`
SpecializedMemory *int64 `json:"specialized_memory,omitempty"`
Name *string `json:"name,omitempty"`
NextStateAfterReboot []string `json:"next_state_after_reboot,omitempty"`
Address *string `json:"address,omitempty"`
Hostname *string `json:"hostname,omitempty"`
State []string `json:"state,omitempty"`
OperatingSystem *string `json:"operating_system,omitempty"`
Owner *string `json:"owner,omitempty"`
Partitions *CSVString `json:"partitions,omitempty"`
Port *int32 `json:"port,omitempty"`
RealMemory *int64 `json:"real_memory,omitempty"`
Comment *string `json:"comment,omitempty"`
Reason *string `json:"reason,omitempty"`
ReasonChangedAt *Uint64NoVal `json:"reason_changed_at,omitempty"`
ReasonSetByUser *string `json:"reason_set_by_user,omitempty"`
ResumeAfter *Uint64NoVal `json:"resume_after,omitempty"`
Reservation *string `json:"reservation,omitempty"`
AllocMemory *int64 `json:"alloc_memory,omitempty"`
AllocCpus *int32 `json:"alloc_cpus,omitempty"`
AllocIdleCpus *int32 `json:"alloc_idle_cpus,omitempty"`
TresUsed *string `json:"tres_used,omitempty"`
TresWeighted *float64 `json:"tres_weighted,omitempty"`
SlurmdStartTime *Uint64NoVal `json:"slurmd_start_time,omitempty"`
Sockets *int32 `json:"sockets,omitempty"`
Threads *int32 `json:"threads,omitempty"`
TemporaryDisk *int32 `json:"temporary_disk,omitempty"`
Weight *int32 `json:"weight,omitempty"`
Tres *string `json:"tres,omitempty"`
Version *string `json:"version,omitempty"`
}
// Nodes is a collection of Node objects (v0.0.40_nodes).
type Nodes []Node
// UpdateNodeMsg represents a node update request (v0.0.40_update_node_msg).
type UpdateNodeMsg struct {
Comment *string `json:"comment,omitempty"`
CpuBind *int32 `json:"cpu_bind,omitempty"`
Extra *string `json:"extra,omitempty"`
Features *CSVString `json:"features,omitempty"`
FeaturesAct *CSVString `json:"features_act,omitempty"`
Gres *string `json:"gres,omitempty"`
Address *HostlistString `json:"address,omitempty"`
Hostname *HostlistString `json:"hostname,omitempty"`
Name *HostlistString `json:"name,omitempty"`
State []string `json:"state,omitempty"`
Reason *string `json:"reason,omitempty"`
ReasonUID *string `json:"reason_uid,omitempty"`
ResumeAfter *Uint32NoVal `json:"resume_after,omitempty"`
Weight *Uint32NoVal `json:"weight,omitempty"`
}
// OpenapiNodesResp represents the response for node queries (v0.0.40_openapi_nodes_resp).
type OpenapiNodesResp struct {
Nodes *Nodes `json:"nodes,omitempty"`
LastUpdate *Uint64NoVal `json:"last_update,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}

View File

@@ -0,0 +1,296 @@
package slurm
import (
"encoding/json"
"testing"
)
func TestNodeRoundTrip(t *testing.T) {
orig := Node{
Architecture: Ptr("x86_64"),
Boards: Ptr(int32(1)),
BootTime: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1234567890))},
ClusterName: Ptr("test-cluster"),
Cores: Ptr(int32(16)),
CpuLoad: Ptr(int32(50)),
FreeMem: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(32768))},
Cpus: Ptr(int32(64)),
Energy: &AcctGatherEnergy{
AverageWatts: Ptr(int32(200)),
ConsumedEnergy: Ptr(int64(15000)),
CurrentWatts: &Uint32NoVal{Set: Ptr(true), Number: Ptr(int64(180))},
LastCollected: Ptr(int64(1234567900)),
},
ExternalSensors: &ExtSensorsData{
CurrentWatts: Ptr(int32(175)),
ConsumedEnergy: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(12000))},
EnergyUpdateTime: Ptr(int64(1234567900)),
},
Power: &PowerMgmtData{
CurrentWatts: Ptr(int32(200)),
PeakWatts: Ptr(int32(350)),
LowestWatts: Ptr(int32(100)),
},
Features: &CSVString{"gpu", "nvme"},
ActiveFeatures: &CSVString{"gpu"},
Gres: Ptr("gpu:4"),
Name: Ptr("node01"),
State: []string{"IDLE", "POWERED_DOWN"},
Hostname: Ptr("node01.example.com"),
Address: Ptr("10.0.0.1"),
Partitions: &CSVString{"normal", "gpu"},
Port: Ptr(int32(6818)),
RealMemory: Ptr(int64(128000)),
Sockets: Ptr(int32(2)),
Threads: Ptr(int32(2)),
Weight: Ptr(int32(1)),
Tres: Ptr("cpu=64,mem=128G"),
Version: Ptr("24.05.5"),
AllocCpus: Ptr(int32(0)),
AllocMemory: Ptr(int64(0)),
SlurmdStartTime: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1234500000))},
ReasonChangedAt: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(0))},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var decoded Node
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if decoded.Name == nil || *decoded.Name != "node01" {
t.Fatalf("name mismatch: %v", decoded.Name)
}
if decoded.Cpus == nil || *decoded.Cpus != 64 {
t.Fatalf("cpus mismatch: %v", decoded.Cpus)
}
if len(decoded.State) != 2 || decoded.State[0] != "IDLE" {
t.Fatalf("state mismatch: %v", decoded.State)
}
if decoded.Energy == nil || decoded.Energy.AverageWatts == nil || *decoded.Energy.AverageWatts != 200 {
t.Fatalf("energy.average_watts mismatch: %v", decoded.Energy)
}
if decoded.ExternalSensors == nil || decoded.ExternalSensors.CurrentWatts == nil || *decoded.ExternalSensors.CurrentWatts != 175 {
t.Fatalf("external_sensors.current_watts mismatch: %v", decoded.ExternalSensors)
}
if decoded.Power == nil || decoded.Power.PeakWatts == nil || *decoded.Power.PeakWatts != 350 {
t.Fatalf("power.peak_watts mismatch: %v", decoded.Power)
}
if decoded.Features == nil || len(*decoded.Features) != 2 || (*decoded.Features)[0] != "gpu" {
t.Fatalf("features mismatch: %v", decoded.Features)
}
if decoded.BootTime == nil || decoded.BootTime.Number == nil || *decoded.BootTime.Number != 1234567890 {
t.Fatalf("boot_time mismatch: %v", decoded.BootTime)
}
}
func TestNodeEmptyRoundTrip(t *testing.T) {
orig := Node{}
data, err := json.Marshal(orig)
if err != nil {
t.Fatalf("marshal: %v", err)
}
if string(data) != "{}" {
t.Fatalf("empty Node should marshal to {}, got %s", data)
}
var decoded Node
if err := json.Unmarshal([]byte(`{}`), &decoded); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if decoded.Name != nil {
t.Fatal("name should be nil for empty object")
}
}
func TestUpdateNodeMsgRoundTrip(t *testing.T) {
orig := UpdateNodeMsg{
Comment: Ptr("maintenance window"),
CpuBind: Ptr(int32(1)),
Extra: Ptr("custom data"),
Features: &CSVString{"new_feat"},
FeaturesAct: &CSVString{"active_feat"},
Gres: Ptr("gpu:2"),
Address: &HostlistString{"node01"},
Hostname: &HostlistString{"node01.example.com"},
Name: &HostlistString{"node01"},
State: []string{"DRAIN"},
Reason: Ptr("scheduled maintenance"),
ReasonUID: Ptr("root"),
ResumeAfter: &Uint32NoVal{Set: Ptr(true), Number: Ptr(int64(3600))},
Weight: &Uint32NoVal{Set: Ptr(true), Number: Ptr(int64(10))},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var decoded UpdateNodeMsg
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if decoded.Comment == nil || *decoded.Comment != "maintenance window" {
t.Fatalf("comment mismatch: %v", decoded.Comment)
}
if len(decoded.State) != 1 || decoded.State[0] != "DRAIN" {
t.Fatalf("state mismatch: %v", decoded.State)
}
if decoded.ResumeAfter == nil || decoded.ResumeAfter.Number == nil || *decoded.ResumeAfter.Number != 3600 {
t.Fatalf("resume_after mismatch: %v", decoded.ResumeAfter)
}
if decoded.Weight == nil || decoded.Weight.Number == nil || *decoded.Weight.Number != 10 {
t.Fatalf("weight mismatch: %v", decoded.Weight)
}
if decoded.Name == nil || len(*decoded.Name) != 1 || (*decoded.Name)[0] != "node01" {
t.Fatalf("name mismatch: %v", decoded.Name)
}
}
func TestOpenapiNodesRespRoundTrip(t *testing.T) {
nodes := Nodes{
{
Name: Ptr("node01"),
State: []string{"IDLE"},
Cpus: Ptr(int32(64)),
RealMemory: Ptr(int64(128000)),
},
{
Name: Ptr("node02"),
State: []string{"ALLOCATED"},
Cpus: Ptr(int32(32)),
RealMemory: Ptr(int64(64000)),
},
}
orig := OpenapiNodesResp{
Nodes: &nodes,
LastUpdate: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1234567890))},
Meta: &OpenapiMeta{
Slurm: &MetaSlurm{
Version: &MetaSlurmVersion{
Major: Ptr("24"),
Micro: Ptr("5"),
Minor: Ptr("05"),
},
},
},
Errors: OpenapiErrors{},
Warnings: OpenapiWarnings{},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var decoded OpenapiNodesResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if decoded.Nodes == nil || len(*decoded.Nodes) != 2 {
t.Fatalf("expected 2 nodes, got %d", len(*decoded.Nodes))
}
nodesSlice := *decoded.Nodes
if nodesSlice[0].Name == nil || *nodesSlice[0].Name != "node01" {
t.Fatalf("nodes[0].name mismatch")
}
if nodesSlice[1].Cpus == nil || *nodesSlice[1].Cpus != 32 {
t.Fatalf("nodes[1].cpus mismatch")
}
if decoded.LastUpdate == nil || decoded.LastUpdate.Number == nil || *decoded.LastUpdate.Number != 1234567890 {
t.Fatalf("last_update mismatch: %v", decoded.LastUpdate)
}
if decoded.Meta == nil || decoded.Meta.Slurm == nil || decoded.Meta.Slurm.Version == nil {
t.Fatal("meta.slurm.version should not be nil")
}
}
func TestAcctGatherEnergyRoundTrip(t *testing.T) {
orig := AcctGatherEnergy{
AverageWatts: Ptr(int32(250)),
BaseConsumedEnergy: Ptr(int64(10000)),
ConsumedEnergy: Ptr(int64(50000)),
CurrentWatts: &Uint32NoVal{Set: Ptr(true), Number: Ptr(int64(210))},
PreviousConsumedEnergy: Ptr(int64(40000)),
LastCollected: Ptr(int64(1234567999)),
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var decoded AcctGatherEnergy
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if decoded.AverageWatts == nil || *decoded.AverageWatts != 250 {
t.Fatalf("average_watts mismatch: %v", decoded.AverageWatts)
}
if decoded.ConsumedEnergy == nil || *decoded.ConsumedEnergy != 50000 {
t.Fatalf("consumed_energy mismatch: %v", decoded.ConsumedEnergy)
}
if decoded.CurrentWatts == nil || decoded.CurrentWatts.Number == nil || *decoded.CurrentWatts.Number != 210 {
t.Fatalf("current_watts mismatch: %v", decoded.CurrentWatts)
}
}
func TestExtSensorsDataRoundTrip(t *testing.T) {
orig := ExtSensorsData{
ConsumedEnergy: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(99999))},
Temperature: &Uint32NoVal{Set: Ptr(true), Number: Ptr(int64(42))},
EnergyUpdateTime: Ptr(int64(1234567900)),
CurrentWatts: Ptr(int32(190)),
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var decoded ExtSensorsData
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if decoded.ConsumedEnergy == nil || decoded.ConsumedEnergy.Number == nil || *decoded.ConsumedEnergy.Number != 99999 {
t.Fatalf("consumed_energy mismatch: %v", decoded.ConsumedEnergy)
}
if decoded.Temperature == nil || decoded.Temperature.Number == nil || *decoded.Temperature.Number != 42 {
t.Fatalf("temperature mismatch: %v", decoded.Temperature)
}
if decoded.CurrentWatts == nil || *decoded.CurrentWatts != 190 {
t.Fatalf("current_watts mismatch: %v", decoded.CurrentWatts)
}
}
func TestPowerMgmtDataRoundTrip(t *testing.T) {
orig := PowerMgmtData{
MaximumWatts: &Uint32NoVal{Set: Ptr(true), Number: Ptr(int64(400))},
CurrentWatts: Ptr(int32(220)),
TotalEnergy: Ptr(int64(500000)),
NewMaximumWatts: Ptr(int32(450)),
PeakWatts: Ptr(int32(380)),
LowestWatts: Ptr(int32(90)),
NewJobTime: &Uint64NoVal{Set: Ptr(true), Number: Ptr(int64(1234567999))},
State: Ptr(int32(1)),
TimeStartDay: Ptr(int64(1234500000)),
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var decoded PowerMgmtData
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if decoded.MaximumWatts == nil || decoded.MaximumWatts.Number == nil || *decoded.MaximumWatts.Number != 400 {
t.Fatalf("maximum_watts mismatch: %v", decoded.MaximumWatts)
}
if decoded.CurrentWatts == nil || *decoded.CurrentWatts != 220 {
t.Fatalf("current_watts mismatch: %v", decoded.CurrentWatts)
}
if decoded.TotalEnergy == nil || *decoded.TotalEnergy != 500000 {
t.Fatalf("total_energy mismatch: %v", decoded.TotalEnergy)
}
if decoded.PeakWatts == nil || *decoded.PeakWatts != 380 {
t.Fatalf("peak_watts mismatch: %v", decoded.PeakWatts)
}
if decoded.NewJobTime == nil || decoded.NewJobTime.Number == nil || *decoded.NewJobTime.Number != 1234567999 {
t.Fatalf("new_job_time mismatch: %v", decoded.NewJobTime)
}
if decoded.State == nil || *decoded.State != 1 {
t.Fatalf("state mismatch: %v", decoded.State)
}
}