feat(slurmdb): add all slurmdb response types and supporting types

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 21:29:22 +08:00
parent f4100f4236
commit 4674dc12a9
2 changed files with 1125 additions and 0 deletions

View File

@@ -0,0 +1,309 @@
package slurm
// ---------------------------------------------------------------------------
// SlurmDBD Response wrapper types — v0.0.40 OpenAPI SlurmDBD schemas
// All fields are optional (pointer types) with json:"name,omitempty".
// ---------------------------------------------------------------------------
// OpenapiSlurmdbdJobsResp is the response for listing SlurmDBD jobs.
// Corresponds to v0.0.40_openapi_slurmdbd_jobs_resp.
type OpenapiSlurmdbdJobsResp struct {
Jobs JobList `json:"jobs,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiSlurmdbdConfigResp is the response for SlurmDBD configuration.
// Corresponds to v0.0.40_openapi_slurmdbd_config_resp.
type OpenapiSlurmdbdConfigResp struct {
Clusters ClusterRecList `json:"clusters,omitempty"`
Tres TresList `json:"tres,omitempty"`
Accounts AccountList `json:"accounts,omitempty"`
Users UserList `json:"users,omitempty"`
Qos QosList `json:"qos,omitempty"`
Wckeys WckeyList `json:"wckeys,omitempty"`
Associations AssocList `json:"associations,omitempty"`
Instances InstanceList `json:"instances,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiSlurmdbdQosResp is the response for listing QOS.
// Corresponds to v0.0.40_openapi_slurmdbd_qos_resp.
type OpenapiSlurmdbdQosResp struct {
Qos QosList `json:"qos,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiSlurmdbdQosRemovedResp is the response for removed QOS.
// Corresponds to v0.0.40_openapi_slurmdbd_qos_removed_resp.
type OpenapiSlurmdbdQosRemovedResp struct {
RemovedQos StringList `json:"removed_qos,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiSlurmdbdStatsResp is the response for SlurmDBD diagnostics.
// Corresponds to v0.0.40_openapi_slurmdbd_stats_resp.
type OpenapiSlurmdbdStatsResp struct {
Statistics *StatsRec `json:"statistics,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiAssocsResp is the response for listing associations.
// Corresponds to v0.0.40_openapi_assocs_resp.
type OpenapiAssocsResp struct {
Associations AssocList `json:"associations,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiAssocsRemovedResp is the response for removed associations.
// Corresponds to v0.0.40_openapi_assocs_removed_resp.
type OpenapiAssocsRemovedResp struct {
RemovedAssociations StringList `json:"removed_associations,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiUsersResp is the response for listing users.
// Corresponds to v0.0.40_openapi_users_resp.
type OpenapiUsersResp struct {
Users UserList `json:"users,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiClustersResp is the response for listing clusters.
// Corresponds to v0.0.40_openapi_clusters_resp.
type OpenapiClustersResp struct {
Clusters ClusterRecList `json:"clusters,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiClustersRemovedResp is the response for deleted clusters.
// Corresponds to v0.0.40_openapi_clusters_removed_resp.
type OpenapiClustersRemovedResp struct {
DeletedClusters StringList `json:"deleted_clusters,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiWckeyResp is the response for listing WCKeys.
// Corresponds to v0.0.40_openapi_wckey_resp.
type OpenapiWckeyResp struct {
Wckeys WckeyList `json:"wckeys,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiWckeyRemovedResp is the response for deleted WCKeys.
// Corresponds to v0.0.40_openapi_wckey_removed_resp.
type OpenapiWckeyRemovedResp struct {
DeletedWckeys StringList `json:"deleted_wckeys,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiAccountsResp is the response for listing accounts.
// Corresponds to v0.0.40_openapi_accounts_resp.
type OpenapiAccountsResp struct {
Accounts AccountList `json:"accounts,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiAccountsRemovedResp is the response for removed accounts.
// Corresponds to v0.0.40_openapi_accounts_removed_resp.
type OpenapiAccountsRemovedResp struct {
RemovedAccounts StringList `json:"removed_accounts,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiInstancesResp is the response for listing instances.
// Corresponds to v0.0.40_openapi_instances_resp.
type OpenapiInstancesResp struct {
Instances InstanceList `json:"instances,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiTresResp is the response for listing TRES.
// Corresponds to v0.0.40_openapi_tres_resp.
// Note: JSON key for TRES is UPPERCASE per OpenAPI spec.
type OpenapiTresResp struct {
TRES TresList `json:"TRES,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiUsersAddCondResp is the response for adding users with conditions.
// Corresponds to v0.0.40_openapi_users_add_cond_resp.
type OpenapiUsersAddCondResp struct {
AssociationCondition *UsersAddCond `json:"association_condition,omitempty"`
User *UserShort `json:"user,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiUsersAddCondRespStr is the string response for adding users with conditions.
// Corresponds to v0.0.40_openapi_users_add_cond_resp_str.
type OpenapiUsersAddCondRespStr struct {
AddedUsers *string `json:"added_users,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiAccountsAddCondResp is the response for adding accounts with conditions.
// Corresponds to v0.0.40_openapi_accounts_add_cond_resp.
type OpenapiAccountsAddCondResp struct {
AssociationCondition *AccountsAddCond `json:"association_condition,omitempty"`
Account *AccountShort `json:"account,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// OpenapiAccountsAddCondRespStr is the string response for adding accounts with conditions.
// Corresponds to v0.0.40_openapi_accounts_add_cond_resp_str.
type OpenapiAccountsAddCondRespStr struct {
AddedAccounts *string `json:"added_accounts,omitempty"`
Meta *OpenapiMeta `json:"meta,omitempty"`
Errors OpenapiErrors `json:"errors,omitempty"`
Warnings OpenapiWarnings `json:"warnings,omitempty"`
}
// ---------------------------------------------------------------------------
// Supporting types for SlurmDBD diagnostics
// ---------------------------------------------------------------------------
// StatsRec represents SlurmDBD diagnostic statistics.
// Corresponds to v0.0.40_stats_rec.
type StatsRec struct {
TimeStart *int64 `json:"time_start,omitempty"`
Rollups RollupStatsList `json:"rollups,omitempty"`
RPCs StatsRpcList `json:"RPCs,omitempty"`
Users StatsUserList `json:"users,omitempty"`
}
// RollupStatsList is a list of RollupStats.
// Corresponds to v0.0.40_rollup_stats.
type RollupStatsList []RollupStats
// RollupStats represents recorded rollup statistics.
type RollupStats struct {
Type *string `json:"type,omitempty"`
LastRun *int32 `json:"last run,omitempty"`
MaxCycle *int64 `json:"max_cycle,omitempty"`
TotalTime *int64 `json:"total_time,omitempty"`
TotalCycles *int64 `json:"total_cycles,omitempty"`
MeanCycles *int64 `json:"mean_cycles,omitempty"`
}
// StatsRpcList is a list of StatsRpc.
// Corresponds to v0.0.40_stats_rpc_list.
type StatsRpcList []StatsRpc
// StatsRpc represents RPC statistics.
// Corresponds to v0.0.40_stats_rpc.
type StatsRpc struct {
RPC *string `json:"rpc,omitempty"`
Count *int32 `json:"count,omitempty"`
Time *StatsRpcTime `json:"time,omitempty"`
}
// StatsRpcTime represents timing information for RPC statistics.
type StatsRpcTime struct {
Average *int64 `json:"average,omitempty"`
Total *int64 `json:"total,omitempty"`
}
// StatsUserList is a list of StatsUser.
// Corresponds to v0.0.40_stats_user_list.
type StatsUserList []StatsUser
// StatsUser represents per-user statistics.
// Corresponds to v0.0.40_stats_user.
type StatsUser struct {
User *string `json:"user,omitempty"`
Count *int32 `json:"count,omitempty"`
Time *StatsUserTime `json:"time,omitempty"`
}
// StatsUserTime represents timing information for user statistics.
type StatsUserTime struct {
Average *int64 `json:"average,omitempty"`
Total *int64 `json:"total,omitempty"`
}
// ---------------------------------------------------------------------------
// UsersAddCond and AccountsAddCond — association condition types
// ---------------------------------------------------------------------------
// UsersAddCond represents filters to select associations for users.
// Corresponds to v0.0.40_users_add_cond.
type UsersAddCond struct {
Accounts StringList `json:"accounts,omitempty"`
Association *AssocRecSet `json:"association,omitempty"`
Clusters StringList `json:"clusters,omitempty"`
Partitions StringList `json:"partitions,omitempty"`
Users StringList `json:"users,omitempty"`
Wckeys StringList `json:"wckeys,omitempty"`
}
// AccountsAddCond represents filters for adding accounts.
// Corresponds to v0.0.40_accounts_add_cond.
type AccountsAddCond struct {
Accounts StringList `json:"accounts,omitempty"`
Association *AssocRecSet `json:"association,omitempty"`
Clusters StringList `json:"clusters,omitempty"`
}
// AssocRecSet represents association limits and options for user/account creation.
// Corresponds to v0.0.40_assoc_rec_set.
type AssocRecSet struct {
Comment *string `json:"comment,omitempty"`
Defaultqos *string `json:"defaultqos,omitempty"`
Grpjobs *Uint32NoVal `json:"grpjobs,omitempty"`
Grpjobsaccrue *Uint32NoVal `json:"grpjobsaccrue,omitempty"`
Grpsubmitjobs *Uint32NoVal `json:"grpsubmitjobs,omitempty"`
Grptres TresList `json:"grptres,omitempty"`
Grptresmins TresList `json:"grptresmins,omitempty"`
Grptresrunmins TresList `json:"grptresrunmins,omitempty"`
Grpwall *Uint32NoVal `json:"grpwall,omitempty"`
Maxjobs *Uint32NoVal `json:"maxjobs,omitempty"`
Maxjobsaccrue *Uint32NoVal `json:"maxjobsaccrue,omitempty"`
Maxsubmitjobs *Uint32NoVal `json:"maxsubmitjobs,omitempty"`
Maxtresminsperjob TresList `json:"maxtresminsperjob,omitempty"`
Maxtresrunmins TresList `json:"maxtresrunmins,omitempty"`
Maxtresperjob TresList `json:"maxtresperjob,omitempty"`
Maxtrespernode TresList `json:"maxtrespernode,omitempty"`
Maxwalldurationperjob *Uint32NoVal `json:"maxwalldurationperjob,omitempty"`
Minpriothresh *Uint32NoVal `json:"minpriothresh,omitempty"`
Parent *string `json:"parent,omitempty"`
Priority *Uint32NoVal `json:"priority,omitempty"`
Qoslevel QosStringIdList `json:"qoslevel,omitempty"`
Fairshare *int32 `json:"fairshare,omitempty"`
}

View File

@@ -0,0 +1,816 @@
package slurm
import (
"encoding/json"
"strings"
"testing"
)
func TestSlurmdbJobsRespRoundTrip(t *testing.T) {
orig := OpenapiSlurmdbdJobsResp{
Jobs: JobList{
{JobID: Ptr(int32(1)), Name: Ptr("test-job")},
{JobID: Ptr(int32(2)), Name: Ptr("other-job")},
},
Meta: &OpenapiMeta{},
Errors: OpenapiErrors{},
Warnings: OpenapiWarnings{},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiSlurmdbdJobsResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Jobs) != 2 {
t.Fatalf("expected 2 jobs, got %d", len(decoded.Jobs))
}
if *decoded.Jobs[0].JobID != 1 {
t.Fatal("first job id mismatch")
}
if *decoded.Jobs[1].Name != "other-job" {
t.Fatal("second job name mismatch")
}
}
func TestSlurmdbConfigRespRoundTrip(t *testing.T) {
orig := OpenapiSlurmdbdConfigResp{
Clusters: ClusterRecList{
{Name: Ptr("cluster1")},
},
Tres: TresList{
{Type: Ptr("cpu"), ID: Ptr(int32(1))},
},
Accounts: AccountList{
{Name: Ptr("acct1")},
},
Users: UserList{
{Name: Ptr("user1")},
},
Qos: QosList{
{Name: Ptr("normal")},
},
Wckeys: WckeyList{
{Name: Ptr("wckey1")},
},
Associations: AssocList{
{Account: Ptr("acct1"), User: Ptr("user1")},
},
Instances: InstanceList{
{InstanceID: Ptr("i-123")},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
// Verify lowercase "tres" key in JSON
raw := string(data)
if strings.Contains(raw, `"TRES"`) {
t.Fatal("OpenapiSlurmdbdConfigResp should use lowercase 'tres', not uppercase 'TRES'")
}
if !strings.Contains(raw, `"tres"`) {
t.Fatal("OpenapiSlurmdbdConfigResp should contain lowercase 'tres' key")
}
var decoded OpenapiSlurmdbdConfigResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Clusters) != 1 || *decoded.Clusters[0].Name != "cluster1" {
t.Fatal("clusters mismatch")
}
if len(decoded.Tres) != 1 || *decoded.Tres[0].Type != "cpu" {
t.Fatal("tres mismatch")
}
if len(decoded.Accounts) != 1 || *decoded.Accounts[0].Name != "acct1" {
t.Fatal("accounts mismatch")
}
if len(decoded.Users) != 1 || *decoded.Users[0].Name != "user1" {
t.Fatal("users mismatch")
}
if len(decoded.Qos) != 1 || *decoded.Qos[0].Name != "normal" {
t.Fatal("qos mismatch")
}
if len(decoded.Wckeys) != 1 || *decoded.Wckeys[0].Name != "wckey1" {
t.Fatal("wckeys mismatch")
}
if len(decoded.Associations) != 1 || *decoded.Associations[0].Account != "acct1" {
t.Fatal("associations mismatch")
}
if len(decoded.Instances) != 1 || *decoded.Instances[0].InstanceID != "i-123" {
t.Fatal("instances mismatch")
}
}
func TestSlurmdbQosRespRoundTrip(t *testing.T) {
orig := OpenapiSlurmdbdQosResp{
Qos: QosList{
{Name: Ptr("high"), ID: Ptr(int32(1))},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiSlurmdbdQosResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Qos) != 1 || *decoded.Qos[0].Name != "high" {
t.Fatal("qos mismatch")
}
}
func TestSlurmdbQosRemovedRespRoundTrip(t *testing.T) {
orig := OpenapiSlurmdbdQosRemovedResp{
RemovedQos: StringList{"normal", "high"},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiSlurmdbdQosRemovedResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.RemovedQos) != 2 || decoded.RemovedQos[0] != "normal" {
t.Fatal("removed_qos mismatch")
}
}
func TestSlurmdbStatsRespRoundTrip(t *testing.T) {
orig := OpenapiSlurmdbdStatsResp{
Statistics: &StatsRec{
TimeStart: Ptr(int64(1700000000)),
Rollups: RollupStatsList{
{Type: Ptr("internal"), MaxCycle: Ptr(int64(5))},
},
RPCs: StatsRpcList{
{RPC: Ptr("SLURMCTLD_GET_JOB_INFO"), Count: Ptr(int32(100))},
},
Users: StatsUserList{
{User: Ptr("root"), Count: Ptr(int32(50))},
},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiSlurmdbdStatsResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.Statistics == nil {
t.Fatal("statistics is nil")
}
if *decoded.Statistics.TimeStart != 1700000000 {
t.Fatal("time_start mismatch")
}
if len(decoded.Statistics.Rollups) != 1 || *decoded.Statistics.Rollups[0].Type != "internal" {
t.Fatal("rollups mismatch")
}
if len(decoded.Statistics.RPCs) != 1 || *decoded.Statistics.RPCs[0].RPC != "SLURMCTLD_GET_JOB_INFO" {
t.Fatal("RPCs mismatch")
}
if len(decoded.Statistics.Users) != 1 || *decoded.Statistics.Users[0].User != "root" {
t.Fatal("users mismatch")
}
}
func TestSlurmdbAssocsRespRoundTrip(t *testing.T) {
orig := OpenapiAssocsResp{
Associations: AssocList{
{Account: Ptr("acct1"), User: Ptr("user1")},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiAssocsResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Associations) != 1 || *decoded.Associations[0].Account != "acct1" {
t.Fatal("associations mismatch")
}
}
func TestSlurmdbAssocsRemovedRespRoundTrip(t *testing.T) {
orig := OpenapiAssocsRemovedResp{
RemovedAssociations: StringList{"acct1_user1_cluster1"},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiAssocsRemovedResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.RemovedAssociations) != 1 || decoded.RemovedAssociations[0] != "acct1_user1_cluster1" {
t.Fatal("removed_associations mismatch")
}
}
func TestSlurmdbUsersRespRoundTrip(t *testing.T) {
orig := OpenapiUsersResp{
Users: UserList{
{Name: Ptr("admin"), AdministratorLevel: []string{"Administrator"}},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiUsersResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Users) != 1 || *decoded.Users[0].Name != "admin" {
t.Fatal("users mismatch")
}
}
func TestSlurmdbClustersRespRoundTrip(t *testing.T) {
orig := OpenapiClustersResp{
Clusters: ClusterRecList{
{Name: Ptr("test-cluster"), Nodes: Ptr("node[1-10]")},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiClustersResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Clusters) != 1 || *decoded.Clusters[0].Name != "test-cluster" {
t.Fatal("clusters mismatch")
}
}
func TestSlurmdbClustersRemovedRespRoundTrip(t *testing.T) {
orig := OpenapiClustersRemovedResp{
DeletedClusters: StringList{"old-cluster"},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiClustersRemovedResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.DeletedClusters) != 1 || decoded.DeletedClusters[0] != "old-cluster" {
t.Fatal("deleted_clusters mismatch")
}
}
func TestSlurmdbWckeyRespRoundTrip(t *testing.T) {
orig := OpenapiWckeyResp{
Wckeys: WckeyList{
{Name: Ptr("wckey1"), Cluster: Ptr("cluster1")},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiWckeyResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Wckeys) != 1 || *decoded.Wckeys[0].Name != "wckey1" {
t.Fatal("wckeys mismatch")
}
}
func TestSlurmdbWckeyRemovedRespRoundTrip(t *testing.T) {
orig := OpenapiWckeyRemovedResp{
DeletedWckeys: StringList{"old-wckey"},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiWckeyRemovedResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.DeletedWckeys) != 1 || decoded.DeletedWckeys[0] != "old-wckey" {
t.Fatal("deleted_wckeys mismatch")
}
}
func TestSlurmdbAccountsRespRoundTrip(t *testing.T) {
orig := OpenapiAccountsResp{
Accounts: AccountList{
{Name: Ptr("science"), Description: Ptr("Science dept"), Organization: Ptr("org1")},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiAccountsResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Accounts) != 1 || *decoded.Accounts[0].Name != "science" {
t.Fatal("accounts mismatch")
}
}
func TestSlurmdbAccountsRemovedRespRoundTrip(t *testing.T) {
orig := OpenapiAccountsRemovedResp{
RemovedAccounts: StringList{"old-acct"},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiAccountsRemovedResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.RemovedAccounts) != 1 || decoded.RemovedAccounts[0] != "old-acct" {
t.Fatal("removed_accounts mismatch")
}
}
func TestSlurmdbInstancesRespRoundTrip(t *testing.T) {
orig := OpenapiInstancesResp{
Instances: InstanceList{
{InstanceID: Ptr("i-abc123"), Cluster: Ptr("cluster1")},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiInstancesResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Instances) != 1 || *decoded.Instances[0].InstanceID != "i-abc123" {
t.Fatal("instances mismatch")
}
}
func TestSlurmdbTresRespRoundTrip(t *testing.T) {
orig := OpenapiTresResp{
TRES: TresList{
{Type: Ptr("cpu"), ID: Ptr(int32(1)), Count: Ptr(int64(100))},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
// Verify UPPERCASE "TRES" key in JSON
raw := string(data)
if !strings.Contains(raw, `"TRES"`) {
t.Fatalf("OpenapiTresResp should use UPPERCASE 'TRES' key, got: %s", raw)
}
if strings.Contains(raw, `"tres"`) {
t.Fatal("OpenapiTresResp should NOT contain lowercase 'tres'")
}
var decoded OpenapiTresResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.TRES) != 1 || *decoded.TRES[0].Type != "cpu" {
t.Fatal("TRES mismatch")
}
}
func TestSlurmdbTresRespUppercaseKey(t *testing.T) {
raw := `{"TRES": [{"type": "mem", "id": 2, "count": 1000}]}`
var decoded OpenapiTresResp
if err := json.Unmarshal([]byte(raw), &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.TRES) != 1 || *decoded.TRES[0].Type != "mem" {
t.Fatal("TRES uppercase key parse failed")
}
}
func TestSlurmdbConfigRespLowercaseTresKey(t *testing.T) {
raw := `{"tres": [{"type": "cpu", "id": 1, "count": 100}]}`
var decoded OpenapiSlurmdbdConfigResp
if err := json.Unmarshal([]byte(raw), &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Tres) != 1 || *decoded.Tres[0].Type != "cpu" {
t.Fatal("config resp lowercase tres key parse failed")
}
}
func TestSlurmdbUsersAddCondRespRoundTrip(t *testing.T) {
orig := OpenapiUsersAddCondResp{
AssociationCondition: &UsersAddCond{
Accounts: StringList{"acct1", "acct2"},
Clusters: StringList{"cluster1"},
Users: StringList{"user1", "user2"},
Wckeys: StringList{"wckey1"},
Partitions: StringList{"partition1"},
},
User: &UserShort{
Adminlevel: []string{"Administrator"},
Defaultaccount: Ptr("acct1"),
Defaultwckey: Ptr("wckey1"),
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiUsersAddCondResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.AssociationCondition == nil {
t.Fatal("association_condition is nil")
}
if len(decoded.AssociationCondition.Accounts) != 2 {
t.Fatal("accounts count mismatch")
}
if decoded.User == nil || *decoded.User.Defaultaccount != "acct1" {
t.Fatal("user mismatch")
}
}
func TestSlurmdbUsersAddCondRespStrRoundTrip(t *testing.T) {
orig := OpenapiUsersAddCondRespStr{
AddedUsers: Ptr("user1,user2,user3"),
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiUsersAddCondRespStr
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.AddedUsers == nil || *decoded.AddedUsers != "user1,user2,user3" {
t.Fatal("added_users mismatch")
}
}
func TestSlurmdbAccountsAddCondRespRoundTrip(t *testing.T) {
orig := OpenapiAccountsAddCondResp{
AssociationCondition: &AccountsAddCond{
Accounts: StringList{"acct1"},
Clusters: StringList{"cluster1"},
},
Account: &AccountShort{
Description: Ptr("Science department"),
Organization: Ptr("university"),
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiAccountsAddCondResp
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.AssociationCondition == nil || len(decoded.AssociationCondition.Accounts) != 1 {
t.Fatal("association_condition mismatch")
}
if decoded.Account == nil || *decoded.Account.Description != "Science department" {
t.Fatal("account mismatch")
}
}
func TestSlurmdbAccountsAddCondRespStrRoundTrip(t *testing.T) {
orig := OpenapiAccountsAddCondRespStr{
AddedAccounts: Ptr("acct1,acct2"),
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded OpenapiAccountsAddCondRespStr
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if decoded.AddedAccounts == nil || *decoded.AddedAccounts != "acct1,acct2" {
t.Fatal("added_accounts mismatch")
}
}
func TestSlurmdbStatsRecRoundTrip(t *testing.T) {
orig := StatsRec{
TimeStart: Ptr(int64(1700000000)),
Rollups: RollupStatsList{
{
Type: Ptr("internal"),
LastRun: Ptr(int32(1700001000)),
MaxCycle: Ptr(int64(10)),
TotalTime: Ptr(int64(100)),
TotalCycles: Ptr(int64(50)),
MeanCycles: Ptr(int64(2)),
},
},
RPCs: StatsRpcList{
{
RPC: Ptr("GET_JOBS"),
Count: Ptr(int32(500)),
Time: &StatsRpcTime{
Average: Ptr(int64(100)),
Total: Ptr(int64(50000)),
},
},
},
Users: StatsUserList{
{
User: Ptr("admin"),
Count: Ptr(int32(200)),
Time: &StatsUserTime{
Average: Ptr(int64(50)),
Total: Ptr(int64(10000)),
},
},
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded StatsRec
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if *decoded.TimeStart != 1700000000 {
t.Fatal("time_start mismatch")
}
if len(decoded.Rollups) != 1 || *decoded.Rollups[0].Type != "internal" {
t.Fatal("rollups mismatch")
}
if *decoded.Rollups[0].MaxCycle != 10 {
t.Fatal("max_cycle mismatch")
}
if len(decoded.RPCs) != 1 || *decoded.RPCs[0].RPC != "GET_JOBS" {
t.Fatal("RPCs mismatch")
}
if decoded.RPCs[0].Time == nil || *decoded.RPCs[0].Time.Average != 100 {
t.Fatal("RPC time.average mismatch")
}
if len(decoded.Users) != 1 || *decoded.Users[0].User != "admin" {
t.Fatal("users mismatch")
}
if decoded.Users[0].Time == nil || *decoded.Users[0].Time.Total != 10000 {
t.Fatal("user time.total mismatch")
}
}
func TestSlurmdbStatsRecRPCsUppercaseKey(t *testing.T) {
raw := `{"time_start": 1700000000, "RPCs": [{"rpc": "TEST", "count": 1}]}`
var decoded StatsRec
if err := json.Unmarshal([]byte(raw), &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.RPCs) != 1 || *decoded.RPCs[0].RPC != "TEST" {
t.Fatal("RPCs uppercase key parse failed")
}
}
func TestSlurmdbRollupStatsRoundTrip(t *testing.T) {
orig := RollupStats{
Type: Ptr("user"),
LastRun: Ptr(int32(1700002000)),
MaxCycle: Ptr(int64(20)),
TotalTime: Ptr(int64(200)),
TotalCycles: Ptr(int64(100)),
MeanCycles: Ptr(int64(2)),
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded RollupStats
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if *decoded.Type != "user" {
t.Fatal("type mismatch")
}
if *decoded.LastRun != 1700002000 {
t.Fatal("last run mismatch")
}
if *decoded.TotalCycles != 100 {
t.Fatal("total_cycles mismatch")
}
}
func TestSlurmdbAssocRecSetRoundTrip(t *testing.T) {
orig := AssocRecSet{
Comment: Ptr("test comment"),
Defaultqos: Ptr("normal"),
Fairshare: Ptr(int32(100)),
Parent: Ptr("parent-acct"),
Grptres: TresList{{Type: Ptr("cpu"), Count: Ptr(int64(1000))}},
Maxtresperjob: TresList{{Type: Ptr("mem"), Count: Ptr(int64(4096))}},
Qoslevel: QosStringIdList{"high", "normal"},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded AssocRecSet
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if *decoded.Comment != "test comment" {
t.Fatal("comment mismatch")
}
if *decoded.Defaultqos != "normal" {
t.Fatal("defaultqos mismatch")
}
if *decoded.Fairshare != 100 {
t.Fatal("fairshare mismatch")
}
if len(decoded.Grptres) != 1 || *decoded.Grptres[0].Type != "cpu" {
t.Fatal("grptres mismatch")
}
if len(decoded.Qoslevel) != 2 || decoded.Qoslevel[0] != "high" {
t.Fatal("qoslevel mismatch")
}
}
func TestSlurmdbUsersAddCondRoundTrip(t *testing.T) {
orig := UsersAddCond{
Accounts: StringList{"acct1"},
Clusters: StringList{"cluster1"},
Partitions: StringList{"part1"},
Users: StringList{"user1", "user2"},
Wckeys: StringList{"wckey1"},
Association: &AssocRecSet{
Comment: Ptr("assoc comment"),
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded UsersAddCond
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Users) != 2 {
t.Fatal("users count mismatch")
}
if decoded.Association == nil || *decoded.Association.Comment != "assoc comment" {
t.Fatal("association mismatch")
}
}
func TestSlurmdbAccountsAddCondRoundTrip(t *testing.T) {
orig := AccountsAddCond{
Accounts: StringList{"acct1", "acct2"},
Clusters: StringList{"cluster1"},
Association: &AssocRecSet{
Fairshare: Ptr(int32(50)),
},
}
data, err := json.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var decoded AccountsAddCond
if err := json.Unmarshal(data, &decoded); err != nil {
t.Fatal(err)
}
if len(decoded.Accounts) != 2 {
t.Fatal("accounts count mismatch")
}
if decoded.Association == nil || *decoded.Association.Fairshare != 50 {
t.Fatal("association mismatch")
}
}
func TestSlurmdbEmptyResponses(t *testing.T) {
types := []interface{}{
OpenapiSlurmdbdJobsResp{},
OpenapiSlurmdbdConfigResp{},
OpenapiSlurmdbdQosResp{},
OpenapiSlurmdbdQosRemovedResp{},
OpenapiSlurmdbdStatsResp{},
OpenapiAssocsResp{},
OpenapiAssocsRemovedResp{},
OpenapiUsersResp{},
OpenapiClustersResp{},
OpenapiClustersRemovedResp{},
OpenapiWckeyResp{},
OpenapiWckeyRemovedResp{},
OpenapiAccountsResp{},
OpenapiAccountsRemovedResp{},
OpenapiInstancesResp{},
OpenapiTresResp{},
OpenapiUsersAddCondResp{},
OpenapiUsersAddCondRespStr{},
OpenapiAccountsAddCondResp{},
OpenapiAccountsAddCondRespStr{},
StatsRec{},
RollupStats{},
StatsRpc{},
StatsUser{},
UsersAddCond{},
AccountsAddCond{},
AssocRecSet{},
}
for i, v := range types {
data, err := json.Marshal(v)
if err != nil {
t.Fatalf("type %d: marshal error: %v", i, err)
}
if string(data) != "{}" {
t.Fatalf("type %d: empty struct should marshal to {}, got %s", i, data)
}
}
}