feat: 添加 Reservation 领域类型和 ReservationsService
包含 ReservationInfo、ReservationCoreSpec 等类型。ReservationsService 提供 GetReservations 和 GetReservation 2 个方法。 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
71
internal/slurm/slurm_reservations.go
Normal file
71
internal/slurm/slurm_reservations.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package slurm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// GetReservationsOptions specifies optional parameters for GetReservations.
|
||||
type GetReservationsOptions struct {
|
||||
UpdateTime *int64 `url:"update_time,omitempty"`
|
||||
}
|
||||
|
||||
// GetReservations lists all reservations.
|
||||
func (s *ReservationsService) GetReservations(ctx context.Context, opts *GetReservationsOptions) (*OpenapiReservationResp, *Response, error) {
|
||||
path := "slurm/v0.0.40/reservations"
|
||||
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", strconv.FormatInt(*opts.UpdateTime, 10))
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
req.URL = u
|
||||
}
|
||||
|
||||
var result OpenapiReservationResp
|
||||
resp, err := s.client.Do(ctx, req, &result)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return &result, resp, nil
|
||||
}
|
||||
|
||||
// GetReservation gets a single reservation by name.
|
||||
func (s *ReservationsService) GetReservation(ctx context.Context, reservationName string, opts *GetReservationsOptions) (*OpenapiReservationResp, *Response, error) {
|
||||
path := fmt.Sprintf("slurm/v0.0.40/reservation/%s", reservationName)
|
||||
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", strconv.FormatInt(*opts.UpdateTime, 10))
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
req.URL = u
|
||||
}
|
||||
|
||||
var result OpenapiReservationResp
|
||||
resp, err := s.client.Do(ctx, req, &result)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return &result, resp, nil
|
||||
}
|
||||
86
internal/slurm/slurm_reservations_test.go
Normal file
86
internal/slurm/slurm_reservations_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package slurm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReservationsService_GetReservations(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/slurm/v0.0.40/reservations", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"reservations": []}`)
|
||||
})
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
client, _ := NewClient(server.URL, nil)
|
||||
resp, _, err := client.Reservations.GetReservations(context.Background(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReservationsService_GetReservation(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/slurm/v0.0.40/reservation/test-reservation", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "GET")
|
||||
fmt.Fprint(w, `{"reservations": [{"name": "test-reservation"}]}`)
|
||||
})
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
client, _ := NewClient(server.URL, nil)
|
||||
resp, _, err := client.Reservations.GetReservation(context.Background(), "test-reservation", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.Reservations == nil {
|
||||
t.Fatal("expected non-nil reservations")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReservationsService_GetReservations_Error(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/slurm/v0.0.40/reservations", 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.Reservations.GetReservations(context.Background(), nil)
|
||||
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 TestReservationsService_GetReservation_Error(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/slurm/v0.0.40/reservation/nonexistent", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprint(w, `{"errors": [{"error": "reservation not found"}]}`)
|
||||
})
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
client, _ := NewClient(server.URL, nil)
|
||||
_, _, err := client.Reservations.GetReservation(context.Background(), "nonexistent", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for 404 response")
|
||||
}
|
||||
}
|
||||
54
internal/slurm/types_reservation.go
Normal file
54
internal/slurm/types_reservation.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package slurm
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reservation types — v0.0.40 reservation schemas
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ReservationCoreSpec represents a single core specialization entry (v0.0.40_reservation_core_spec).
|
||||
type ReservationCoreSpec struct {
|
||||
Node *string `json:"node,omitempty"`
|
||||
Core *string `json:"core,omitempty"`
|
||||
}
|
||||
|
||||
// ReservationInfoCoreSpec is a collection of ReservationCoreSpec objects (v0.0.40_reservation_info_core_spec).
|
||||
type ReservationInfoCoreSpec []ReservationCoreSpec
|
||||
|
||||
// ReservationPurgeCompleted represents purge_completed settings for a reservation.
|
||||
type ReservationPurgeCompleted struct {
|
||||
Time *Uint32NoVal `json:"time,omitempty"`
|
||||
}
|
||||
|
||||
// ReservationInfo represents a Slurm reservation (v0.0.40_reservation_info).
|
||||
type ReservationInfo struct {
|
||||
Accounts *string `json:"accounts,omitempty"`
|
||||
BurstBuffer *string `json:"burst_buffer,omitempty"`
|
||||
CoreCount *int32 `json:"core_count,omitempty"`
|
||||
CoreSpecializations *ReservationInfoCoreSpec `json:"core_specializations,omitempty"`
|
||||
EndTime *Uint64NoVal `json:"end_time,omitempty"`
|
||||
Features *string `json:"features,omitempty"`
|
||||
Flags []string `json:"flags,omitempty"`
|
||||
Groups *string `json:"groups,omitempty"`
|
||||
Licenses *string `json:"licenses,omitempty"`
|
||||
MaxStartDelay *int32 `json:"max_start_delay,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
NodeCount *int32 `json:"node_count,omitempty"`
|
||||
NodeList *string `json:"node_list,omitempty"`
|
||||
Partition *string `json:"partition,omitempty"`
|
||||
PurgeCompleted *ReservationPurgeCompleted `json:"purge_completed,omitempty"`
|
||||
StartTime *Uint64NoVal `json:"start_time,omitempty"`
|
||||
Watts *Uint32NoVal `json:"watts,omitempty"`
|
||||
TRES *string `json:"tres,omitempty"`
|
||||
Users *string `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
// ReservationInfoMsg is a collection of ReservationInfo objects (v0.0.40_reservation_info_msg).
|
||||
type ReservationInfoMsg []ReservationInfo
|
||||
|
||||
// OpenapiReservationResp represents the response for reservation queries (v0.0.40_openapi_reservation_resp).
|
||||
type OpenapiReservationResp struct {
|
||||
Reservations *ReservationInfoMsg `json:"reservations,omitempty"`
|
||||
LastUpdate *Uint64NoVal `json:"last_update,omitempty"`
|
||||
Meta *OpenapiMeta `json:"meta,omitempty"`
|
||||
Errors OpenapiErrors `json:"errors,omitempty"`
|
||||
Warnings OpenapiWarnings `json:"warnings,omitempty"`
|
||||
}
|
||||
273
internal/slurm/types_reservation_test.go
Normal file
273
internal/slurm/types_reservation_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package slurm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReservationCoreSpecRoundTrip(t *testing.T) {
|
||||
orig := ReservationCoreSpec{
|
||||
Node: Ptr("node01"),
|
||||
Core: Ptr("0-3"),
|
||||
}
|
||||
data, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
var decoded ReservationCoreSpec
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if decoded.Node == nil || *decoded.Node != "node01" {
|
||||
t.Fatalf("node mismatch: %v", decoded.Node)
|
||||
}
|
||||
if decoded.Core == nil || *decoded.Core != "0-3" {
|
||||
t.Fatalf("core mismatch: %v", decoded.Core)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReservationCoreSpecEmptyRoundTrip(t *testing.T) {
|
||||
orig := ReservationCoreSpec{}
|
||||
data, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
if string(data) != "{}" {
|
||||
t.Fatalf("empty ReservationCoreSpec should marshal to {}, got %s", data)
|
||||
}
|
||||
var decoded ReservationCoreSpec
|
||||
if err := json.Unmarshal([]byte(`{}`), &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if decoded.Node != nil || decoded.Core != nil {
|
||||
t.Fatal("all fields should be nil for empty object")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReservationInfoCoreSpecRoundTrip(t *testing.T) {
|
||||
orig := ReservationInfoCoreSpec{
|
||||
{Node: Ptr("node01"), Core: Ptr("0-3")},
|
||||
{Node: Ptr("node02"), Core: Ptr("4-7")},
|
||||
}
|
||||
data, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
var decoded ReservationInfoCoreSpec
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if len(decoded) != 2 {
|
||||
t.Fatalf("expected 2 entries, got %d", len(decoded))
|
||||
}
|
||||
if decoded[0].Node == nil || *decoded[0].Node != "node01" {
|
||||
t.Fatalf("entry[0].node mismatch: %v", decoded[0].Node)
|
||||
}
|
||||
if decoded[1].Core == nil || *decoded[1].Core != "4-7" {
|
||||
t.Fatalf("entry[1].core mismatch: %v", decoded[1].Core)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReservationInfoRoundTrip(t *testing.T) {
|
||||
orig := ReservationInfo{
|
||||
Accounts: Ptr("admin,dev"),
|
||||
BurstBuffer: Ptr("bb_test"),
|
||||
CoreCount: Ptr(int32(16)),
|
||||
CoreSpecializations: &ReservationInfoCoreSpec{
|
||||
{Node: Ptr("node01"), Core: Ptr("0-3")},
|
||||
},
|
||||
EndTime: &Uint64NoVal{
|
||||
Set: Ptr(true),
|
||||
Number: Ptr(int64(1700000000)),
|
||||
},
|
||||
Features: Ptr("gpu,nvme"),
|
||||
Flags: []string{"MAINT", "DAILY", "IGNORE_JOBS"},
|
||||
Groups: Ptr("slurm"),
|
||||
Licenses: Ptr("matlab:2"),
|
||||
MaxStartDelay: Ptr(int32(300)),
|
||||
Name: Ptr("test_resv"),
|
||||
NodeCount: Ptr(int32(4)),
|
||||
NodeList: Ptr("node[01-04]"),
|
||||
Partition: Ptr("normal"),
|
||||
PurgeCompleted: &ReservationPurgeCompleted{
|
||||
Time: &Uint32NoVal{Set: Ptr(true), Number: Ptr(int64(3600))},
|
||||
},
|
||||
StartTime: &Uint64NoVal{
|
||||
Set: Ptr(true),
|
||||
Number: Ptr(int64(1699990000)),
|
||||
},
|
||||
Watts: &Uint32NoVal{Set: Ptr(true), Infinite: Ptr(true)},
|
||||
TRES: Ptr("billing=16"),
|
||||
Users: Ptr("user1,user2"),
|
||||
}
|
||||
data, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
var decoded ReservationInfo
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if decoded.Name == nil || *decoded.Name != "test_resv" {
|
||||
t.Fatalf("name mismatch: %v", decoded.Name)
|
||||
}
|
||||
if decoded.Accounts == nil || *decoded.Accounts != "admin,dev" {
|
||||
t.Fatalf("accounts mismatch: %v", decoded.Accounts)
|
||||
}
|
||||
if decoded.CoreCount == nil || *decoded.CoreCount != 16 {
|
||||
t.Fatalf("core_count mismatch: %v", decoded.CoreCount)
|
||||
}
|
||||
if decoded.CoreSpecializations == nil || len(*decoded.CoreSpecializations) != 1 {
|
||||
t.Fatalf("core_specializations mismatch: %v", decoded.CoreSpecializations)
|
||||
}
|
||||
if decoded.EndTime == nil || decoded.EndTime.Number == nil || *decoded.EndTime.Number != 1700000000 {
|
||||
t.Fatalf("end_time mismatch: %v", decoded.EndTime)
|
||||
}
|
||||
if len(decoded.Flags) != 3 || decoded.Flags[0] != "MAINT" || decoded.Flags[2] != "IGNORE_JOBS" {
|
||||
t.Fatalf("flags mismatch: %v", decoded.Flags)
|
||||
}
|
||||
if decoded.MaxStartDelay == nil || *decoded.MaxStartDelay != 300 {
|
||||
t.Fatalf("max_start_delay mismatch: %v", decoded.MaxStartDelay)
|
||||
}
|
||||
if decoded.NodeCount == nil || *decoded.NodeCount != 4 {
|
||||
t.Fatalf("node_count mismatch: %v", decoded.NodeCount)
|
||||
}
|
||||
if decoded.PurgeCompleted == nil || decoded.PurgeCompleted.Time == nil || decoded.PurgeCompleted.Time.Number == nil || *decoded.PurgeCompleted.Time.Number != 3600 {
|
||||
t.Fatalf("purge_completed.time mismatch: %v", decoded.PurgeCompleted)
|
||||
}
|
||||
if decoded.StartTime == nil || decoded.StartTime.Number == nil || *decoded.StartTime.Number != 1699990000 {
|
||||
t.Fatalf("start_time mismatch: %v", decoded.StartTime)
|
||||
}
|
||||
if decoded.Watts == nil || decoded.Watts.Infinite == nil || !*decoded.Watts.Infinite {
|
||||
t.Fatalf("watts mismatch: %v", decoded.Watts)
|
||||
}
|
||||
if decoded.TRES == nil || *decoded.TRES != "billing=16" {
|
||||
t.Fatalf("tres mismatch: %v", decoded.TRES)
|
||||
}
|
||||
if decoded.Users == nil || *decoded.Users != "user1,user2" {
|
||||
t.Fatalf("users mismatch: %v", decoded.Users)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReservationInfoEmptyRoundTrip(t *testing.T) {
|
||||
orig := ReservationInfo{}
|
||||
data, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
if string(data) != "{}" {
|
||||
t.Fatalf("empty ReservationInfo should marshal to {}, got %s", data)
|
||||
}
|
||||
var decoded ReservationInfo
|
||||
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 TestReservationInfoMsgRoundTrip(t *testing.T) {
|
||||
orig := ReservationInfoMsg{
|
||||
{Name: Ptr("resv1"), NodeCount: Ptr(int32(2))},
|
||||
{Name: Ptr("resv2"), NodeCount: Ptr(int32(4)), Flags: []string{"MAINT", "FLEX"}},
|
||||
}
|
||||
data, err := json.Marshal(orig)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal: %v", err)
|
||||
}
|
||||
var decoded ReservationInfoMsg
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if len(decoded) != 2 {
|
||||
t.Fatalf("expected 2 reservations, got %d", len(decoded))
|
||||
}
|
||||
if decoded[0].Name == nil || *decoded[0].Name != "resv1" {
|
||||
t.Fatalf("reservations[0].name mismatch: %v", decoded[0].Name)
|
||||
}
|
||||
if decoded[1].Name == nil || *decoded[1].Name != "resv2" {
|
||||
t.Fatalf("reservations[1].name mismatch: %v", decoded[1].Name)
|
||||
}
|
||||
if len(decoded[1].Flags) != 2 || decoded[1].Flags[1] != "FLEX" {
|
||||
t.Fatalf("reservations[1].flags mismatch: %v", decoded[1].Flags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenapiReservationRespRoundTrip(t *testing.T) {
|
||||
reservations := ReservationInfoMsg{
|
||||
{
|
||||
Name: Ptr("maint_window"),
|
||||
Partition: Ptr("normal"),
|
||||
NodeList: Ptr("node[01-10]"),
|
||||
Flags: []string{"MAINT", "IGNORE_JOBS"},
|
||||
EndTime: &Uint64NoVal{
|
||||
Set: Ptr(true),
|
||||
Number: Ptr(int64(1700050000)),
|
||||
},
|
||||
StartTime: &Uint64NoVal{
|
||||
Set: Ptr(true),
|
||||
Number: Ptr(int64(1700000000)),
|
||||
},
|
||||
CoreCount: Ptr(int32(160)),
|
||||
NodeCount: Ptr(int32(10)),
|
||||
},
|
||||
{
|
||||
Name: Ptr("gpu_resv"),
|
||||
TRES: Ptr("gres/gpu=8"),
|
||||
Users: Ptr("researcher1"),
|
||||
Watts: &Uint32NoVal{Set: Ptr(true), Infinite: Ptr(true)},
|
||||
},
|
||||
}
|
||||
orig := OpenapiReservationResp{
|
||||
Reservations: &reservations,
|
||||
LastUpdate: &Uint64NoVal{
|
||||
Set: Ptr(true),
|
||||
Number: Ptr(int64(1700010000)),
|
||||
},
|
||||
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 OpenapiReservationResp
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
if decoded.Reservations == nil || len(*decoded.Reservations) != 2 {
|
||||
t.Fatalf("expected 2 reservations, got %d", len(*decoded.Reservations))
|
||||
}
|
||||
rs := *decoded.Reservations
|
||||
if rs[0].Name == nil || *rs[0].Name != "maint_window" {
|
||||
t.Fatalf("reservations[0].name mismatch")
|
||||
}
|
||||
if rs[0].EndTime == nil || rs[0].EndTime.Number == nil || *rs[0].EndTime.Number != 1700050000 {
|
||||
t.Fatalf("reservations[0].end_time mismatch")
|
||||
}
|
||||
if len(rs[0].Flags) != 2 || rs[0].Flags[0] != "MAINT" {
|
||||
t.Fatalf("reservations[0].flags mismatch: %v", rs[0].Flags)
|
||||
}
|
||||
if rs[1].TRES == nil || *rs[1].TRES != "gres/gpu=8" {
|
||||
t.Fatalf("reservations[1].tres mismatch: %v", rs[1].TRES)
|
||||
}
|
||||
if rs[1].Watts == nil || rs[1].Watts.Infinite == nil || !*rs[1].Watts.Infinite {
|
||||
t.Fatalf("reservations[1].watts.infinite mismatch")
|
||||
}
|
||||
if decoded.LastUpdate == nil || decoded.LastUpdate.Number == nil || *decoded.LastUpdate.Number != 1700010000 {
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user