diff --git a/pkg/client/api/v0042/account.go b/pkg/client/api/v0042/account.go new file mode 100644 index 0000000..a242ee2 --- /dev/null +++ b/pkg/client/api/v0042/account.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0042 + +import ( + "context" + "errors" + "net/http" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type AccountInterface interface { + CreateAccount(ctx context.Context, req any) (string, error) + UpdateAccount(ctx context.Context, name string, req any) error + DeleteAccount(ctx context.Context, name string) error + GetAccount(ctx context.Context, name string) (*types.V0042Account, error) + ListAccount(ctx context.Context) (*types.V0042AccountList, error) +} + +var _ AccountInterface = &SlurmClient{} + +// CreateAccount implements AccountInterface. +func (c *SlurmClient) CreateAccount(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0042Account) + if !ok { + return "", errors.New("expected req to be api.V0042Account") + } + body := api.SlurmdbV0042PostAccountsJSONRequestBody{ + Accounts: api.V0042AccountList{r}, + } + res, err := c.SlurmdbV0042PostAccountsWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + return r.Name, nil +} + +// UpdateAccount implements AccountInterface. POST is upsert in slurmdbd. +func (c *SlurmClient) UpdateAccount(ctx context.Context, name string, req any) error { + r, ok := req.(api.V0042Account) + if !ok { + return errors.New("expected req to be api.V0042Account") + } + r.Name = name + _, err := c.CreateAccount(ctx, r) + return err +} + +// DeleteAccount implements AccountInterface. +func (c *SlurmClient) DeleteAccount(ctx context.Context, name string) error { + res, err := c.SlurmdbV0042DeleteAccountWithResponse(ctx, name) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetAccount implements AccountInterface. +func (c *SlurmClient) GetAccount(ctx context.Context, name string) (*types.V0042Account, error) { + params := &api.SlurmdbV0042GetAccountParams{} + res, err := c.SlurmdbV0042GetAccountWithResponse(ctx, name, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil || len(res.JSON200.Accounts) == 0 { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + out := &types.V0042Account{} + utils.RemarshalOrDie(res.JSON200.Accounts[0], out) + return out, nil +} + +// ListAccount implements AccountInterface. +func (c *SlurmClient) ListAccount(ctx context.Context) (*types.V0042AccountList, error) { + params := &api.SlurmdbV0042GetAccountsParams{} + res, err := c.SlurmdbV0042GetAccountsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0042AccountList{ + Items: make([]types.V0042Account, len(res.JSON200.Accounts)), + } + for i, item := range res.JSON200.Accounts { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0042/account_test.go b/pkg/client/api/v0042/account_test.go new file mode 100644 index 0000000..4b0fd11 --- /dev/null +++ b/pkg/client/api/v0042/account_test.go @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0042 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0042/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0042/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestAccountInterface_Compiles(t *testing.T) { + var _ AccountInterface = &SlurmClient{} +} + +func TestSlurmClient_GetAccount(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0042Account + wantErr bool + }{ + { + name: "Not Found", + client: fake.NewFakeClient(), + want: nil, + wantErr: true, + }, + { + name: "Found", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0042GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAccountResponse, error) { + return &api.SlurmdbV0042GetAccountResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0042OpenapiAccountsResp{ + Accounts: api.V0042AccountList{{Name: "research"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0042Account{V0042Account: api.V0042Account{Name: "research"}}, + wantErr: false, + }, + { + name: "HTTP Status != 200", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0042GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAccountResponse, error) { + return &api.SlurmdbV0042GetAccountResponse{ + HTTPResponse: &http.Response{ + Status: http.StatusText(http.StatusInternalServerError), + StatusCode: http.StatusInternalServerError, + }, + JSONDefault: &api.V0042OpenapiAccountsResp{ + Errors: &[]api.V0042OpenapiError{{Error: ptr.To("error 1")}}, + }, + }, nil + }, + }). + Build(), + want: nil, + wantErr: true, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0042GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAccountResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetAccount(context.Background(), "research") + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_ListAccount(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0042AccountList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0042AccountList{Items: make([]types.V0042Account, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAccountsWithResponse: func(ctx context.Context, params *api.SlurmdbV0042GetAccountsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAccountsResponse, error) { + return &api.SlurmdbV0042GetAccountsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0042OpenapiAccountsResp{ + Accounts: api.V0042AccountList{{Name: "a"}, {Name: "b"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0042AccountList{Items: []types.V0042Account{ + {V0042Account: api.V0042Account{Name: "a"}}, + {V0042Account: api.V0042Account{Name: "b"}}, + }}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListAccount(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateAccount(t *testing.T) { + tests := []struct { + name string + req any + want string + wantErr bool + }{ + {name: "default", req: api.V0042Account{Name: "research"}, want: "research", wantErr: false}, + {name: "invalid type provided", req: api.V0042User{Name: "research"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + got, err := c.CreateAccount(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SlurmClient.CreateAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_DeleteAccount(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + if err := c.DeleteAccount(context.Background(), "research"); err != nil { + t.Errorf("SlurmClient.DeleteAccount() error = %v, wantErr false", err) + } +} diff --git a/pkg/client/api/v0042/association.go b/pkg/client/api/v0042/association.go new file mode 100644 index 0000000..a335a63 --- /dev/null +++ b/pkg/client/api/v0042/association.go @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0042 + +import ( + "context" + "errors" + "net/http" + "strings" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type AssocInterface interface { + CreateAssoc(ctx context.Context, req any) (string, error) + UpdateAssoc(ctx context.Context, key string, req any) error + DeleteAssoc(ctx context.Context, assoc api.V0042Assoc) error + GetAssoc(ctx context.Context, key string) (*types.V0042Assoc, error) + ListAssoc(ctx context.Context) (*types.V0042AssocList, error) +} + +var _ AssocInterface = &SlurmClient{} + +// parseAssocKey splits a "cluster/account/user/partition" association key. +func parseAssocKey(key string) (cluster, account, user, partition string) { + parts := strings.SplitN(key, "/", 4) + for len(parts) < 4 { + parts = append(parts, "") + } + return parts[0], parts[1], parts[2], parts[3] +} + +func nonEmptyPtr(s string) *string { + if s == "" { + return nil + } + return ptr.To(s) +} + +// CreateAssoc implements AssocInterface (POST is upsert). It returns the +// composite key derived from the request so callers can refresh by identity. +func (c *SlurmClient) CreateAssoc(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0042Assoc) + if !ok { + return "", errors.New("expected req to be api.V0042Assoc") + } + body := api.SlurmdbV0042PostAssociationsJSONRequestBody{ + Associations: api.V0042AssocList{r}, + } + res, err := c.SlurmdbV0042PostAssociationsWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + assoc := &types.V0042Assoc{V0042Assoc: r} + return string(assoc.GetKey()), nil +} + +// UpdateAssoc implements AssocInterface. POST is upsert in slurmdbd; the +// association identity is pinned from key so the update targets the intended +// association regardless of the identity fields set on req. +func (c *SlurmClient) UpdateAssoc(ctx context.Context, key string, req any) error { + r, ok := req.(api.V0042Assoc) + if !ok { + return errors.New("expected req to be api.V0042Assoc") + } + cluster, account, user, partition := parseAssocKey(key) + r.Cluster = nonEmptyPtr(cluster) + r.Account = nonEmptyPtr(account) + r.User = user + r.Partition = nonEmptyPtr(partition) + _, err := c.CreateAssoc(ctx, r) + return err +} + +// DeleteAssoc implements AssocInterface using a filter param set. +func (c *SlurmClient) DeleteAssoc(ctx context.Context, assoc api.V0042Assoc) error { + params := &api.SlurmdbV0042DeleteAssociationParams{ + Account: assoc.Account, + User: nonEmptyPtr(assoc.User), + Cluster: assoc.Cluster, + Partition: assoc.Partition, + } + res, err := c.SlurmdbV0042DeleteAssociationWithResponse(ctx, params) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetAssoc implements AssocInterface. There is no get-by-key endpoint, so the +// composite key is decomposed into server-side filter params to avoid listing +// every association. +func (c *SlurmClient) GetAssoc(ctx context.Context, key string) (*types.V0042Assoc, error) { + cluster, account, user, partition := parseAssocKey(key) + params := &api.SlurmdbV0042GetAssociationsParams{ + Account: nonEmptyPtr(account), + Cluster: nonEmptyPtr(cluster), + User: nonEmptyPtr(user), + Partition: nonEmptyPtr(partition), + } + res, err := c.SlurmdbV0042GetAssociationsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + for _, item := range res.JSON200.Associations { + out := &types.V0042Assoc{} + utils.RemarshalOrDie(item, out) + if string(out.GetKey()) == key { + return out, nil + } + } + return nil, errors.New(http.StatusText(http.StatusNotFound)) +} + +// ListAssoc implements AssocInterface. +func (c *SlurmClient) ListAssoc(ctx context.Context) (*types.V0042AssocList, error) { + params := &api.SlurmdbV0042GetAssociationsParams{} + res, err := c.SlurmdbV0042GetAssociationsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0042AssocList{ + Items: make([]types.V0042Assoc, len(res.JSON200.Associations)), + } + for i, item := range res.JSON200.Associations { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0042/association_test.go b/pkg/client/api/v0042/association_test.go new file mode 100644 index 0000000..c075a5c --- /dev/null +++ b/pkg/client/api/v0042/association_test.go @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0042 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0042/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0042/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestAssocInterface_Compiles(t *testing.T) { + var _ AssocInterface = &SlurmClient{} +} + +func TestSlurmClient_ListAssoc(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0042AssocList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0042AssocList{Items: make([]types.V0042Assoc, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0042GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAssociationsResponse, error) { + return &api.SlurmdbV0042GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0042OpenapiAssocsResp{ + Associations: api.V0042AssocList{ + {User: "alice", Account: ptr.To("research")}, + }, + }, + }, nil + }, + }). + Build(), + want: &types.V0042AssocList{Items: []types.V0042Assoc{ + {V0042Assoc: api.V0042Assoc{User: "alice", Account: ptr.To("research")}}, + }}, + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0042GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAssociationsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListAssoc(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListAssoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListAssoc() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateAssoc(t *testing.T) { + tests := []struct { + name string + req any + wantKey string + wantErr bool + }{ + { + name: "returns composite key from request", + req: api.V0042Assoc{Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + wantKey: "linux/research/alice/", + wantErr: false, + }, + {name: "invalid type provided", req: api.V0042Account{Name: "research"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + gotKey, err := c.CreateAssoc(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateAssoc() error = %v, wantErr %v", err, tt.wantErr) + } + if gotKey != tt.wantKey { + t.Errorf("SlurmClient.CreateAssoc() key = %q, want %q", gotKey, tt.wantKey) + } + }) + } +} + +func TestSlurmClient_UpdateAssoc(t *testing.T) { + var gotBody api.V0042OpenapiAssocsResp + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042PostAssociationsWithResponse: func(ctx context.Context, body api.V0042OpenapiAssocsResp, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042PostAssociationsResponse, error) { + gotBody = body + return &api.SlurmdbV0042PostAssociationsResponse{HTTPResponse: &fake.HttpSuccess}, nil + }, + }). + Build()} + + // req identity fields deliberately differ from key; key must win. + req := api.V0042Assoc{Cluster: ptr.To("other"), Account: ptr.To("other"), User: "bob"} + if err := c.UpdateAssoc(context.Background(), "linux/research/alice/", req); err != nil { + t.Fatalf("SlurmClient.UpdateAssoc() error = %v", err) + } + if len(gotBody.Associations) != 1 { + t.Fatalf("SlurmClient.UpdateAssoc() posted %d associations, want 1", len(gotBody.Associations)) + } + got := gotBody.Associations[0] + if ptr.Deref(got.Cluster, "") != "linux" || ptr.Deref(got.Account, "") != "research" || got.User != "alice" || got.Partition != nil { + t.Errorf("SlurmClient.UpdateAssoc() pinned identity = %+v, want cluster=linux account=research user=alice partition=nil", got) + } + + // invalid type provided + if err := c.UpdateAssoc(context.Background(), "linux/research/alice/", api.V0042Account{Name: "x"}); err == nil { + t.Errorf("SlurmClient.UpdateAssoc() expected error for invalid req type") + } +} + +func TestSlurmClient_GetAssoc(t *testing.T) { + matching := func() api.ClientWithResponsesInterface { + return fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0042GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAssociationsResponse, error) { + return &api.SlurmdbV0042GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0042OpenapiAssocsResp{ + Associations: api.V0042AssocList{ + {Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + }, + }, + }, nil + }, + }). + Build() + } + tests := []struct { + name string + key string + client api.ClientWithResponsesInterface + want *types.V0042Assoc + wantErr bool + }{ + { + name: "found by key", + key: "linux/research/alice/", + client: matching(), + want: &types.V0042Assoc{V0042Assoc: api.V0042Assoc{Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}}, + }, + { + name: "not found - empty result", + key: "linux/research/bob/", + client: fake.NewFakeClient(), + wantErr: true, + }, + { + name: "not found - no key match", + key: "linux/research/bob/", + client: matching(), + wantErr: true, + }, + { + name: "HTTP error", + key: "linux/research/alice/", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0042GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAssociationsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetAssoc(context.Background(), tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetAssoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetAssoc() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_GetAssoc_FiltersServerSide(t *testing.T) { + var gotParams *api.SlurmdbV0042GetAssociationsParams + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0042GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetAssociationsResponse, error) { + gotParams = params + return &api.SlurmdbV0042GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0042OpenapiAssocsResp{ + Associations: api.V0042AssocList{ + {Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + }, + }, + }, nil + }, + }). + Build()} + + if _, err := c.GetAssoc(context.Background(), "linux/research/alice/"); err != nil { + t.Fatalf("SlurmClient.GetAssoc() error = %v", err) + } + if gotParams == nil { + t.Fatal("SlurmClient.GetAssoc() did not call the associations endpoint") + } + if ptr.Deref(gotParams.Cluster, "") != "linux" || ptr.Deref(gotParams.Account, "") != "research" || ptr.Deref(gotParams.User, "") != "alice" { + t.Errorf("SlurmClient.GetAssoc() filter params = %+v, want cluster=linux account=research user=alice", gotParams) + } + if gotParams.Partition != nil { + t.Errorf("SlurmClient.GetAssoc() partition param = %v, want nil", gotParams.Partition) + } +} + +func TestSlurmClient_DeleteAssoc(t *testing.T) { + tests := []struct { + name string + assoc api.V0042Assoc + wantUserParam *string + }{ + { + name: "with user", + assoc: api.V0042Assoc{Account: ptr.To("research"), User: "alice"}, + wantUserParam: ptr.To("alice"), + }, + { + name: "empty user omitted", + assoc: api.V0042Assoc{Account: ptr.To("research")}, + wantUserParam: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var gotParams *api.SlurmdbV0042DeleteAssociationParams + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042DeleteAssociationWithResponse: func(ctx context.Context, params *api.SlurmdbV0042DeleteAssociationParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042DeleteAssociationResponse, error) { + gotParams = params + return &api.SlurmdbV0042DeleteAssociationResponse{HTTPResponse: &fake.HttpSuccess, JSON200: &api.V0042OpenapiAssocsRemovedResp{}}, nil + }, + }). + Build()} + if err := c.DeleteAssoc(context.Background(), tt.assoc); err != nil { + t.Fatalf("SlurmClient.DeleteAssoc() error = %v", err) + } + if !reflect.DeepEqual(gotParams.User, tt.wantUserParam) { + t.Errorf("SlurmClient.DeleteAssoc() User param = %v, want %v", ptr.Deref(gotParams.User, ""), ptr.Deref(tt.wantUserParam, "")) + } + }) + } +} diff --git a/pkg/client/api/v0042/user.go b/pkg/client/api/v0042/user.go new file mode 100644 index 0000000..d456847 --- /dev/null +++ b/pkg/client/api/v0042/user.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0042 + +import ( + "context" + "errors" + "net/http" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type UserInterface interface { + CreateUser(ctx context.Context, req any) (string, error) + UpdateUser(ctx context.Context, name string, req any) error + DeleteUser(ctx context.Context, name string) error + GetUser(ctx context.Context, name string) (*types.V0042User, error) + ListUser(ctx context.Context) (*types.V0042UserList, error) +} + +var _ UserInterface = &SlurmClient{} + +// CreateUser implements UserInterface. +func (c *SlurmClient) CreateUser(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0042User) + if !ok { + return "", errors.New("expected req to be api.V0042User") + } + body := api.SlurmdbV0042PostUsersJSONRequestBody{ + Users: api.V0042UserList{r}, + } + res, err := c.SlurmdbV0042PostUsersWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + return r.Name, nil +} + +// UpdateUser implements UserInterface. POST is upsert in slurmdbd. +func (c *SlurmClient) UpdateUser(ctx context.Context, name string, req any) error { + r, ok := req.(api.V0042User) + if !ok { + return errors.New("expected req to be api.V0042User") + } + r.Name = name + _, err := c.CreateUser(ctx, r) + return err +} + +// DeleteUser implements UserInterface. +func (c *SlurmClient) DeleteUser(ctx context.Context, name string) error { + res, err := c.SlurmdbV0042DeleteUserWithResponse(ctx, name) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetUser implements UserInterface. +func (c *SlurmClient) GetUser(ctx context.Context, name string) (*types.V0042User, error) { + params := &api.SlurmdbV0042GetUserParams{} + res, err := c.SlurmdbV0042GetUserWithResponse(ctx, name, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil || len(res.JSON200.Users) == 0 { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + out := &types.V0042User{} + utils.RemarshalOrDie(res.JSON200.Users[0], out) + return out, nil +} + +// ListUser implements UserInterface. +func (c *SlurmClient) ListUser(ctx context.Context) (*types.V0042UserList, error) { + params := &api.SlurmdbV0042GetUsersParams{} + res, err := c.SlurmdbV0042GetUsersWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0042UserList{ + Items: make([]types.V0042User, len(res.JSON200.Users)), + } + for i, item := range res.JSON200.Users { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0042/user_test.go b/pkg/client/api/v0042/user_test.go new file mode 100644 index 0000000..1faf256 --- /dev/null +++ b/pkg/client/api/v0042/user_test.go @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0042 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0042/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0042/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestUserInterface_Compiles(t *testing.T) { + var _ UserInterface = &SlurmClient{} +} + +func TestSlurmClient_GetUser(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0042User + wantErr bool + }{ + { + name: "Not Found", + client: fake.NewFakeClient(), + want: nil, + wantErr: true, + }, + { + name: "Found", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetUserWithResponse: func(ctx context.Context, name string, params *api.SlurmdbV0042GetUserParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetUserResponse, error) { + return &api.SlurmdbV0042GetUserResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0042OpenapiUsersResp{ + Users: api.V0042UserList{{Name: "alice"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0042User{V0042User: api.V0042User{Name: "alice"}}, + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetUserWithResponse: func(ctx context.Context, name string, params *api.SlurmdbV0042GetUserParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetUserResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetUser(context.Background(), "alice") + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_ListUser(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0042UserList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0042UserList{Items: make([]types.V0042User, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0042GetUsersWithResponse: func(ctx context.Context, params *api.SlurmdbV0042GetUsersParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0042GetUsersResponse, error) { + return &api.SlurmdbV0042GetUsersResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0042OpenapiUsersResp{ + Users: api.V0042UserList{{Name: "alice"}, {Name: "bob"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0042UserList{Items: []types.V0042User{ + {V0042User: api.V0042User{Name: "alice"}}, + {V0042User: api.V0042User{Name: "bob"}}, + }}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListUser(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateUser(t *testing.T) { + tests := []struct { + name string + req any + want string + wantErr bool + }{ + {name: "default", req: api.V0042User{Name: "alice"}, want: "alice", wantErr: false}, + {name: "invalid type provided", req: api.V0042Account{Name: "alice"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + got, err := c.CreateUser(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SlurmClient.CreateUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_DeleteUser(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + if err := c.DeleteUser(context.Background(), "alice"); err != nil { + t.Errorf("SlurmClient.DeleteUser() error = %v, wantErr false", err) + } +} diff --git a/pkg/client/api/v0042/v0042.go b/pkg/client/api/v0042/v0042.go index 77bf7f4..2954099 100644 --- a/pkg/client/api/v0042/v0042.go +++ b/pkg/client/api/v0042/v0042.go @@ -23,12 +23,15 @@ const ( type ClientInterface interface { api.ClientWithResponsesInterface + AccountInterface + AssocInterface ControllerPingInfoInterface JobInfoInterface NodeInterface PartitionInterface ReconfigureInterface StatsInterface + UserInterface } type SlurmClient struct { diff --git a/pkg/client/api/v0043/account.go b/pkg/client/api/v0043/account.go new file mode 100644 index 0000000..e9a94fa --- /dev/null +++ b/pkg/client/api/v0043/account.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0043 + +import ( + "context" + "errors" + "net/http" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type AccountInterface interface { + CreateAccount(ctx context.Context, req any) (string, error) + UpdateAccount(ctx context.Context, name string, req any) error + DeleteAccount(ctx context.Context, name string) error + GetAccount(ctx context.Context, name string) (*types.V0043Account, error) + ListAccount(ctx context.Context) (*types.V0043AccountList, error) +} + +var _ AccountInterface = &SlurmClient{} + +// CreateAccount implements AccountInterface. +func (c *SlurmClient) CreateAccount(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0043Account) + if !ok { + return "", errors.New("expected req to be api.V0043Account") + } + body := api.SlurmdbV0043PostAccountsJSONRequestBody{ + Accounts: api.V0043AccountList{r}, + } + res, err := c.SlurmdbV0043PostAccountsWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + return r.Name, nil +} + +// UpdateAccount implements AccountInterface. POST is upsert in slurmdbd. +func (c *SlurmClient) UpdateAccount(ctx context.Context, name string, req any) error { + r, ok := req.(api.V0043Account) + if !ok { + return errors.New("expected req to be api.V0043Account") + } + r.Name = name + _, err := c.CreateAccount(ctx, r) + return err +} + +// DeleteAccount implements AccountInterface. +func (c *SlurmClient) DeleteAccount(ctx context.Context, name string) error { + res, err := c.SlurmdbV0043DeleteAccountWithResponse(ctx, name) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetAccount implements AccountInterface. +func (c *SlurmClient) GetAccount(ctx context.Context, name string) (*types.V0043Account, error) { + params := &api.SlurmdbV0043GetAccountParams{} + res, err := c.SlurmdbV0043GetAccountWithResponse(ctx, name, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil || len(res.JSON200.Accounts) == 0 { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + out := &types.V0043Account{} + utils.RemarshalOrDie(res.JSON200.Accounts[0], out) + return out, nil +} + +// ListAccount implements AccountInterface. +func (c *SlurmClient) ListAccount(ctx context.Context) (*types.V0043AccountList, error) { + params := &api.SlurmdbV0043GetAccountsParams{} + res, err := c.SlurmdbV0043GetAccountsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0043AccountList{ + Items: make([]types.V0043Account, len(res.JSON200.Accounts)), + } + for i, item := range res.JSON200.Accounts { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0043/account_test.go b/pkg/client/api/v0043/account_test.go new file mode 100644 index 0000000..47d4272 --- /dev/null +++ b/pkg/client/api/v0043/account_test.go @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0043 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0043/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0043/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestAccountInterface_Compiles(t *testing.T) { + var _ AccountInterface = &SlurmClient{} +} + +func TestSlurmClient_GetAccount(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0043Account + wantErr bool + }{ + { + name: "Not Found", + client: fake.NewFakeClient(), + want: nil, + wantErr: true, + }, + { + name: "Found", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0043GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAccountResponse, error) { + return &api.SlurmdbV0043GetAccountResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0043OpenapiAccountsResp{ + Accounts: api.V0043AccountList{{Name: "research"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0043Account{V0043Account: api.V0043Account{Name: "research"}}, + wantErr: false, + }, + { + name: "HTTP Status != 200", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0043GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAccountResponse, error) { + return &api.SlurmdbV0043GetAccountResponse{ + HTTPResponse: &http.Response{ + Status: http.StatusText(http.StatusInternalServerError), + StatusCode: http.StatusInternalServerError, + }, + JSONDefault: &api.V0043OpenapiAccountsResp{ + Errors: &[]api.V0043OpenapiError{{Error: ptr.To("error 1")}}, + }, + }, nil + }, + }). + Build(), + want: nil, + wantErr: true, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0043GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAccountResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetAccount(context.Background(), "research") + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_ListAccount(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0043AccountList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0043AccountList{Items: make([]types.V0043Account, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAccountsWithResponse: func(ctx context.Context, params *api.SlurmdbV0043GetAccountsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAccountsResponse, error) { + return &api.SlurmdbV0043GetAccountsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0043OpenapiAccountsResp{ + Accounts: api.V0043AccountList{{Name: "a"}, {Name: "b"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0043AccountList{Items: []types.V0043Account{ + {V0043Account: api.V0043Account{Name: "a"}}, + {V0043Account: api.V0043Account{Name: "b"}}, + }}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListAccount(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateAccount(t *testing.T) { + tests := []struct { + name string + req any + want string + wantErr bool + }{ + {name: "default", req: api.V0043Account{Name: "research"}, want: "research", wantErr: false}, + {name: "invalid type provided", req: api.V0043User{Name: "research"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + got, err := c.CreateAccount(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SlurmClient.CreateAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_DeleteAccount(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + if err := c.DeleteAccount(context.Background(), "research"); err != nil { + t.Errorf("SlurmClient.DeleteAccount() error = %v, wantErr false", err) + } +} diff --git a/pkg/client/api/v0043/association.go b/pkg/client/api/v0043/association.go new file mode 100644 index 0000000..5fa019a --- /dev/null +++ b/pkg/client/api/v0043/association.go @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0043 + +import ( + "context" + "errors" + "net/http" + "strings" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type AssocInterface interface { + CreateAssoc(ctx context.Context, req any) (string, error) + UpdateAssoc(ctx context.Context, key string, req any) error + DeleteAssoc(ctx context.Context, assoc api.V0043Assoc) error + GetAssoc(ctx context.Context, key string) (*types.V0043Assoc, error) + ListAssoc(ctx context.Context) (*types.V0043AssocList, error) +} + +var _ AssocInterface = &SlurmClient{} + +// parseAssocKey splits a "cluster/account/user/partition" association key. +func parseAssocKey(key string) (cluster, account, user, partition string) { + parts := strings.SplitN(key, "/", 4) + for len(parts) < 4 { + parts = append(parts, "") + } + return parts[0], parts[1], parts[2], parts[3] +} + +func nonEmptyPtr(s string) *string { + if s == "" { + return nil + } + return ptr.To(s) +} + +// CreateAssoc implements AssocInterface (POST is upsert). It returns the +// composite key derived from the request so callers can refresh by identity. +func (c *SlurmClient) CreateAssoc(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0043Assoc) + if !ok { + return "", errors.New("expected req to be api.V0043Assoc") + } + body := api.SlurmdbV0043PostAssociationsJSONRequestBody{ + Associations: api.V0043AssocList{r}, + } + res, err := c.SlurmdbV0043PostAssociationsWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + assoc := &types.V0043Assoc{V0043Assoc: r} + return string(assoc.GetKey()), nil +} + +// UpdateAssoc implements AssocInterface. POST is upsert in slurmdbd; the +// association identity is pinned from key so the update targets the intended +// association regardless of the identity fields set on req. +func (c *SlurmClient) UpdateAssoc(ctx context.Context, key string, req any) error { + r, ok := req.(api.V0043Assoc) + if !ok { + return errors.New("expected req to be api.V0043Assoc") + } + cluster, account, user, partition := parseAssocKey(key) + r.Cluster = nonEmptyPtr(cluster) + r.Account = nonEmptyPtr(account) + r.User = user + r.Partition = nonEmptyPtr(partition) + _, err := c.CreateAssoc(ctx, r) + return err +} + +// DeleteAssoc implements AssocInterface using a filter param set. +func (c *SlurmClient) DeleteAssoc(ctx context.Context, assoc api.V0043Assoc) error { + params := &api.SlurmdbV0043DeleteAssociationParams{ + Account: assoc.Account, + User: nonEmptyPtr(assoc.User), + Cluster: assoc.Cluster, + Partition: assoc.Partition, + } + res, err := c.SlurmdbV0043DeleteAssociationWithResponse(ctx, params) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetAssoc implements AssocInterface. There is no get-by-key endpoint, so the +// composite key is decomposed into server-side filter params to avoid listing +// every association. +func (c *SlurmClient) GetAssoc(ctx context.Context, key string) (*types.V0043Assoc, error) { + cluster, account, user, partition := parseAssocKey(key) + params := &api.SlurmdbV0043GetAssociationsParams{ + Account: nonEmptyPtr(account), + Cluster: nonEmptyPtr(cluster), + User: nonEmptyPtr(user), + Partition: nonEmptyPtr(partition), + } + res, err := c.SlurmdbV0043GetAssociationsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + for _, item := range res.JSON200.Associations { + out := &types.V0043Assoc{} + utils.RemarshalOrDie(item, out) + if string(out.GetKey()) == key { + return out, nil + } + } + return nil, errors.New(http.StatusText(http.StatusNotFound)) +} + +// ListAssoc implements AssocInterface. +func (c *SlurmClient) ListAssoc(ctx context.Context) (*types.V0043AssocList, error) { + params := &api.SlurmdbV0043GetAssociationsParams{} + res, err := c.SlurmdbV0043GetAssociationsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0043AssocList{ + Items: make([]types.V0043Assoc, len(res.JSON200.Associations)), + } + for i, item := range res.JSON200.Associations { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0043/association_test.go b/pkg/client/api/v0043/association_test.go new file mode 100644 index 0000000..d417dac --- /dev/null +++ b/pkg/client/api/v0043/association_test.go @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0043 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0043/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0043/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestAssocInterface_Compiles(t *testing.T) { + var _ AssocInterface = &SlurmClient{} +} + +func TestSlurmClient_ListAssoc(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0043AssocList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0043AssocList{Items: make([]types.V0043Assoc, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0043GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAssociationsResponse, error) { + return &api.SlurmdbV0043GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0043OpenapiAssocsResp{ + Associations: api.V0043AssocList{ + {User: "alice", Account: ptr.To("research")}, + }, + }, + }, nil + }, + }). + Build(), + want: &types.V0043AssocList{Items: []types.V0043Assoc{ + {V0043Assoc: api.V0043Assoc{User: "alice", Account: ptr.To("research")}}, + }}, + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0043GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAssociationsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListAssoc(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListAssoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListAssoc() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateAssoc(t *testing.T) { + tests := []struct { + name string + req any + wantKey string + wantErr bool + }{ + { + name: "returns composite key from request", + req: api.V0043Assoc{Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + wantKey: "linux/research/alice/", + wantErr: false, + }, + {name: "invalid type provided", req: api.V0043Account{Name: "research"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + gotKey, err := c.CreateAssoc(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateAssoc() error = %v, wantErr %v", err, tt.wantErr) + } + if gotKey != tt.wantKey { + t.Errorf("SlurmClient.CreateAssoc() key = %q, want %q", gotKey, tt.wantKey) + } + }) + } +} + +func TestSlurmClient_UpdateAssoc(t *testing.T) { + var gotBody api.V0043OpenapiAssocsResp + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043PostAssociationsWithResponse: func(ctx context.Context, body api.V0043OpenapiAssocsResp, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043PostAssociationsResponse, error) { + gotBody = body + return &api.SlurmdbV0043PostAssociationsResponse{HTTPResponse: &fake.HttpSuccess}, nil + }, + }). + Build()} + + // req identity fields deliberately differ from key; key must win. + req := api.V0043Assoc{Cluster: ptr.To("other"), Account: ptr.To("other"), User: "bob"} + if err := c.UpdateAssoc(context.Background(), "linux/research/alice/", req); err != nil { + t.Fatalf("SlurmClient.UpdateAssoc() error = %v", err) + } + if len(gotBody.Associations) != 1 { + t.Fatalf("SlurmClient.UpdateAssoc() posted %d associations, want 1", len(gotBody.Associations)) + } + got := gotBody.Associations[0] + if ptr.Deref(got.Cluster, "") != "linux" || ptr.Deref(got.Account, "") != "research" || got.User != "alice" || got.Partition != nil { + t.Errorf("SlurmClient.UpdateAssoc() pinned identity = %+v, want cluster=linux account=research user=alice partition=nil", got) + } + + // invalid type provided + if err := c.UpdateAssoc(context.Background(), "linux/research/alice/", api.V0043Account{Name: "x"}); err == nil { + t.Errorf("SlurmClient.UpdateAssoc() expected error for invalid req type") + } +} + +func TestSlurmClient_GetAssoc(t *testing.T) { + matching := func() api.ClientWithResponsesInterface { + return fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0043GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAssociationsResponse, error) { + return &api.SlurmdbV0043GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0043OpenapiAssocsResp{ + Associations: api.V0043AssocList{ + {Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + }, + }, + }, nil + }, + }). + Build() + } + tests := []struct { + name string + key string + client api.ClientWithResponsesInterface + want *types.V0043Assoc + wantErr bool + }{ + { + name: "found by key", + key: "linux/research/alice/", + client: matching(), + want: &types.V0043Assoc{V0043Assoc: api.V0043Assoc{Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}}, + }, + { + name: "not found - empty result", + key: "linux/research/bob/", + client: fake.NewFakeClient(), + wantErr: true, + }, + { + name: "not found - no key match", + key: "linux/research/bob/", + client: matching(), + wantErr: true, + }, + { + name: "HTTP error", + key: "linux/research/alice/", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0043GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAssociationsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetAssoc(context.Background(), tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetAssoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetAssoc() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_GetAssoc_FiltersServerSide(t *testing.T) { + var gotParams *api.SlurmdbV0043GetAssociationsParams + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0043GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetAssociationsResponse, error) { + gotParams = params + return &api.SlurmdbV0043GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0043OpenapiAssocsResp{ + Associations: api.V0043AssocList{ + {Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + }, + }, + }, nil + }, + }). + Build()} + + if _, err := c.GetAssoc(context.Background(), "linux/research/alice/"); err != nil { + t.Fatalf("SlurmClient.GetAssoc() error = %v", err) + } + if gotParams == nil { + t.Fatal("SlurmClient.GetAssoc() did not call the associations endpoint") + } + if ptr.Deref(gotParams.Cluster, "") != "linux" || ptr.Deref(gotParams.Account, "") != "research" || ptr.Deref(gotParams.User, "") != "alice" { + t.Errorf("SlurmClient.GetAssoc() filter params = %+v, want cluster=linux account=research user=alice", gotParams) + } + if gotParams.Partition != nil { + t.Errorf("SlurmClient.GetAssoc() partition param = %v, want nil", gotParams.Partition) + } +} + +func TestSlurmClient_DeleteAssoc(t *testing.T) { + tests := []struct { + name string + assoc api.V0043Assoc + wantUserParam *string + }{ + { + name: "with user", + assoc: api.V0043Assoc{Account: ptr.To("research"), User: "alice"}, + wantUserParam: ptr.To("alice"), + }, + { + name: "empty user omitted", + assoc: api.V0043Assoc{Account: ptr.To("research")}, + wantUserParam: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var gotParams *api.SlurmdbV0043DeleteAssociationParams + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043DeleteAssociationWithResponse: func(ctx context.Context, params *api.SlurmdbV0043DeleteAssociationParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043DeleteAssociationResponse, error) { + gotParams = params + return &api.SlurmdbV0043DeleteAssociationResponse{HTTPResponse: &fake.HttpSuccess, JSON200: &api.V0043OpenapiAssocsRemovedResp{}}, nil + }, + }). + Build()} + if err := c.DeleteAssoc(context.Background(), tt.assoc); err != nil { + t.Fatalf("SlurmClient.DeleteAssoc() error = %v", err) + } + if !reflect.DeepEqual(gotParams.User, tt.wantUserParam) { + t.Errorf("SlurmClient.DeleteAssoc() User param = %v, want %v", ptr.Deref(gotParams.User, ""), ptr.Deref(tt.wantUserParam, "")) + } + }) + } +} diff --git a/pkg/client/api/v0043/user.go b/pkg/client/api/v0043/user.go new file mode 100644 index 0000000..e224637 --- /dev/null +++ b/pkg/client/api/v0043/user.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0043 + +import ( + "context" + "errors" + "net/http" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type UserInterface interface { + CreateUser(ctx context.Context, req any) (string, error) + UpdateUser(ctx context.Context, name string, req any) error + DeleteUser(ctx context.Context, name string) error + GetUser(ctx context.Context, name string) (*types.V0043User, error) + ListUser(ctx context.Context) (*types.V0043UserList, error) +} + +var _ UserInterface = &SlurmClient{} + +// CreateUser implements UserInterface. +func (c *SlurmClient) CreateUser(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0043User) + if !ok { + return "", errors.New("expected req to be api.V0043User") + } + body := api.SlurmdbV0043PostUsersJSONRequestBody{ + Users: api.V0043UserList{r}, + } + res, err := c.SlurmdbV0043PostUsersWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + return r.Name, nil +} + +// UpdateUser implements UserInterface. POST is upsert in slurmdbd. +func (c *SlurmClient) UpdateUser(ctx context.Context, name string, req any) error { + r, ok := req.(api.V0043User) + if !ok { + return errors.New("expected req to be api.V0043User") + } + r.Name = name + _, err := c.CreateUser(ctx, r) + return err +} + +// DeleteUser implements UserInterface. +func (c *SlurmClient) DeleteUser(ctx context.Context, name string) error { + res, err := c.SlurmdbV0043DeleteUserWithResponse(ctx, name) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetUser implements UserInterface. +func (c *SlurmClient) GetUser(ctx context.Context, name string) (*types.V0043User, error) { + params := &api.SlurmdbV0043GetUserParams{} + res, err := c.SlurmdbV0043GetUserWithResponse(ctx, name, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil || len(res.JSON200.Users) == 0 { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + out := &types.V0043User{} + utils.RemarshalOrDie(res.JSON200.Users[0], out) + return out, nil +} + +// ListUser implements UserInterface. +func (c *SlurmClient) ListUser(ctx context.Context) (*types.V0043UserList, error) { + params := &api.SlurmdbV0043GetUsersParams{} + res, err := c.SlurmdbV0043GetUsersWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0043UserList{ + Items: make([]types.V0043User, len(res.JSON200.Users)), + } + for i, item := range res.JSON200.Users { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0043/user_test.go b/pkg/client/api/v0043/user_test.go new file mode 100644 index 0000000..c73867e --- /dev/null +++ b/pkg/client/api/v0043/user_test.go @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0043 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0043/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0043/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestUserInterface_Compiles(t *testing.T) { + var _ UserInterface = &SlurmClient{} +} + +func TestSlurmClient_GetUser(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0043User + wantErr bool + }{ + { + name: "Not Found", + client: fake.NewFakeClient(), + want: nil, + wantErr: true, + }, + { + name: "Found", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetUserWithResponse: func(ctx context.Context, name string, params *api.SlurmdbV0043GetUserParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetUserResponse, error) { + return &api.SlurmdbV0043GetUserResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0043OpenapiUsersResp{ + Users: api.V0043UserList{{Name: "alice"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0043User{V0043User: api.V0043User{Name: "alice"}}, + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetUserWithResponse: func(ctx context.Context, name string, params *api.SlurmdbV0043GetUserParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetUserResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetUser(context.Background(), "alice") + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_ListUser(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0043UserList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0043UserList{Items: make([]types.V0043User, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0043GetUsersWithResponse: func(ctx context.Context, params *api.SlurmdbV0043GetUsersParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0043GetUsersResponse, error) { + return &api.SlurmdbV0043GetUsersResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0043OpenapiUsersResp{ + Users: api.V0043UserList{{Name: "alice"}, {Name: "bob"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0043UserList{Items: []types.V0043User{ + {V0043User: api.V0043User{Name: "alice"}}, + {V0043User: api.V0043User{Name: "bob"}}, + }}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListUser(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateUser(t *testing.T) { + tests := []struct { + name string + req any + want string + wantErr bool + }{ + {name: "default", req: api.V0043User{Name: "alice"}, want: "alice", wantErr: false}, + {name: "invalid type provided", req: api.V0043Account{Name: "alice"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + got, err := c.CreateUser(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SlurmClient.CreateUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_DeleteUser(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + if err := c.DeleteUser(context.Background(), "alice"); err != nil { + t.Errorf("SlurmClient.DeleteUser() error = %v, wantErr false", err) + } +} diff --git a/pkg/client/api/v0043/v0043.go b/pkg/client/api/v0043/v0043.go index 7bfa3d0..25d6c04 100644 --- a/pkg/client/api/v0043/v0043.go +++ b/pkg/client/api/v0043/v0043.go @@ -24,12 +24,15 @@ const ( type ClientInterface interface { api.ClientWithResponsesInterface + AccountInterface + AssocInterface ControllerPingInfoInterface JobInfoInterface NodeInterface PartitionInterface ReconfigureInterface StatsInterface + UserInterface } type SlurmClient struct { diff --git a/pkg/client/api/v0044/account.go b/pkg/client/api/v0044/account.go new file mode 100644 index 0000000..d132689 --- /dev/null +++ b/pkg/client/api/v0044/account.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0044 + +import ( + "context" + "errors" + "net/http" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type AccountInterface interface { + CreateAccount(ctx context.Context, req any) (string, error) + UpdateAccount(ctx context.Context, name string, req any) error + DeleteAccount(ctx context.Context, name string) error + GetAccount(ctx context.Context, name string) (*types.V0044Account, error) + ListAccount(ctx context.Context) (*types.V0044AccountList, error) +} + +var _ AccountInterface = &SlurmClient{} + +// CreateAccount implements AccountInterface. +func (c *SlurmClient) CreateAccount(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0044Account) + if !ok { + return "", errors.New("expected req to be api.V0044Account") + } + body := api.SlurmdbV0044PostAccountsJSONRequestBody{ + Accounts: api.V0044AccountList{r}, + } + res, err := c.SlurmdbV0044PostAccountsWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + return r.Name, nil +} + +// UpdateAccount implements AccountInterface. POST is upsert in slurmdbd. +func (c *SlurmClient) UpdateAccount(ctx context.Context, name string, req any) error { + r, ok := req.(api.V0044Account) + if !ok { + return errors.New("expected req to be api.V0044Account") + } + r.Name = name + _, err := c.CreateAccount(ctx, r) + return err +} + +// DeleteAccount implements AccountInterface. +func (c *SlurmClient) DeleteAccount(ctx context.Context, name string) error { + res, err := c.SlurmdbV0044DeleteAccountWithResponse(ctx, name) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetAccount implements AccountInterface. +func (c *SlurmClient) GetAccount(ctx context.Context, name string) (*types.V0044Account, error) { + params := &api.SlurmdbV0044GetAccountParams{} + res, err := c.SlurmdbV0044GetAccountWithResponse(ctx, name, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil || len(res.JSON200.Accounts) == 0 { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + out := &types.V0044Account{} + utils.RemarshalOrDie(res.JSON200.Accounts[0], out) + return out, nil +} + +// ListAccount implements AccountInterface. +func (c *SlurmClient) ListAccount(ctx context.Context) (*types.V0044AccountList, error) { + params := &api.SlurmdbV0044GetAccountsParams{} + res, err := c.SlurmdbV0044GetAccountsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0044AccountList{ + Items: make([]types.V0044Account, len(res.JSON200.Accounts)), + } + for i, item := range res.JSON200.Accounts { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0044/account_test.go b/pkg/client/api/v0044/account_test.go new file mode 100644 index 0000000..c9dc2ba --- /dev/null +++ b/pkg/client/api/v0044/account_test.go @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0044 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0044/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0044/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestAccountInterface_Compiles(t *testing.T) { + var _ AccountInterface = &SlurmClient{} +} + +func TestSlurmClient_GetAccount(t *testing.T) { + type fields struct { + ClientWithResponsesInterface api.ClientWithResponsesInterface + } + type args struct { + ctx context.Context + name string + } + tests := []struct { + name string + fields fields + args args + want *types.V0044Account + wantErr bool + }{ + { + name: "Not Found", + fields: fields{ + ClientWithResponsesInterface: fake.NewFakeClient(), + }, + args: args{ + ctx: context.Background(), + name: "research", + }, + want: nil, + wantErr: true, + }, + { + name: "Found", + fields: fields{ + ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0044GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAccountResponse, error) { + res := &api.SlurmdbV0044GetAccountResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0044OpenapiAccountsResp{ + Accounts: api.V0044AccountList{ + {Name: "research"}, + }, + }, + } + return res, nil + }, + }). + Build(), + }, + args: args{ + ctx: context.Background(), + name: "research", + }, + want: &types.V0044Account{ + V0044Account: api.V0044Account{Name: "research"}, + }, + wantErr: false, + }, + { + name: "HTTP Status != 200", + fields: fields{ + ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0044GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAccountResponse, error) { + res := &api.SlurmdbV0044GetAccountResponse{ + HTTPResponse: &http.Response{ + Status: http.StatusText(http.StatusInternalServerError), + StatusCode: http.StatusInternalServerError, + }, + JSONDefault: &api.V0044OpenapiAccountsResp{ + Errors: &[]api.V0044OpenapiError{ + {Error: ptr.To("error 1")}, + }, + }, + } + return res, nil + }, + }). + Build(), + }, + args: args{ + ctx: context.Background(), + name: "research", + }, + want: nil, + wantErr: true, + }, + { + name: "HTTP Error", + fields: fields{ + ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0044GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAccountResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + }, + args: args{ + ctx: context.Background(), + name: "research", + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ + ClientWithResponsesInterface: tt.fields.ClientWithResponsesInterface, + } + got, err := c.GetAccount(tt.args.ctx, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_ListAccount(t *testing.T) { + type fields struct { + ClientWithResponsesInterface api.ClientWithResponsesInterface + } + tests := []struct { + name string + fields fields + want *types.V0044AccountList + wantErr bool + }{ + { + name: "Empty list", + fields: fields{ + ClientWithResponsesInterface: fake.NewFakeClient(), + }, + want: &types.V0044AccountList{ + Items: make([]types.V0044Account, 0), + }, + wantErr: false, + }, + { + name: "Non-empty list", + fields: fields{ + ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAccountsWithResponse: func(ctx context.Context, params *api.SlurmdbV0044GetAccountsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAccountsResponse, error) { + res := &api.SlurmdbV0044GetAccountsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0044OpenapiAccountsResp{ + Accounts: api.V0044AccountList{ + {Name: "a"}, + {Name: "b"}, + }, + }, + } + return res, nil + }, + }). + Build(), + }, + want: &types.V0044AccountList{ + Items: []types.V0044Account{ + {V0044Account: api.V0044Account{Name: "a"}}, + {V0044Account: api.V0044Account{Name: "b"}}, + }, + }, + wantErr: false, + }, + { + name: "HTTP Error", + fields: fields{ + ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAccountsWithResponse: func(ctx context.Context, params *api.SlurmdbV0044GetAccountsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAccountsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ + ClientWithResponsesInterface: tt.fields.ClientWithResponsesInterface, + } + got, err := c.ListAccount(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateAccount(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + req any + want string + wantErr bool + }{ + { + name: "default", + client: fake.NewFakeClient(), + req: api.V0044Account{Name: "research"}, + want: "research", + wantErr: false, + }, + { + name: "invalid type provided", + client: fake.NewFakeClient(), + req: api.V0044User{Name: "research"}, + wantErr: true, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044PostAccountsWithResponse: func(ctx context.Context, body api.SlurmdbV0044PostAccountsJSONRequestBody, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044PostAccountsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + req: api.V0044Account{Name: "research"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.CreateAccount(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SlurmClient.CreateAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_DeleteAccount(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + wantErr bool + }{ + { + name: "account does not exist", + client: fake.NewFakeClient(), + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044DeleteAccountWithResponse: func(ctx context.Context, accountName string, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044DeleteAccountResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + err := c.DeleteAccount(context.Background(), "research") + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.DeleteAccount() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/client/api/v0044/association.go b/pkg/client/api/v0044/association.go new file mode 100644 index 0000000..fb6fc49 --- /dev/null +++ b/pkg/client/api/v0044/association.go @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0044 + +import ( + "context" + "errors" + "net/http" + "strings" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type AssocInterface interface { + CreateAssoc(ctx context.Context, req any) (string, error) + UpdateAssoc(ctx context.Context, key string, req any) error + DeleteAssoc(ctx context.Context, assoc api.V0044Assoc) error + GetAssoc(ctx context.Context, key string) (*types.V0044Assoc, error) + ListAssoc(ctx context.Context) (*types.V0044AssocList, error) +} + +var _ AssocInterface = &SlurmClient{} + +// parseAssocKey splits a "cluster/account/user/partition" association key. +func parseAssocKey(key string) (cluster, account, user, partition string) { + parts := strings.SplitN(key, "/", 4) + for len(parts) < 4 { + parts = append(parts, "") + } + return parts[0], parts[1], parts[2], parts[3] +} + +func nonEmptyPtr(s string) *string { + if s == "" { + return nil + } + return ptr.To(s) +} + +// CreateAssoc implements AssocInterface (POST is upsert). It returns the +// composite key derived from the request so callers can refresh by identity. +func (c *SlurmClient) CreateAssoc(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0044Assoc) + if !ok { + return "", errors.New("expected req to be api.V0044Assoc") + } + body := api.SlurmdbV0044PostAssociationsJSONRequestBody{ + Associations: api.V0044AssocList{r}, + } + res, err := c.SlurmdbV0044PostAssociationsWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + assoc := &types.V0044Assoc{V0044Assoc: r} + return string(assoc.GetKey()), nil +} + +// UpdateAssoc implements AssocInterface. POST is upsert in slurmdbd; the +// association identity is pinned from key so the update targets the intended +// association regardless of the identity fields set on req. +func (c *SlurmClient) UpdateAssoc(ctx context.Context, key string, req any) error { + r, ok := req.(api.V0044Assoc) + if !ok { + return errors.New("expected req to be api.V0044Assoc") + } + cluster, account, user, partition := parseAssocKey(key) + r.Cluster = nonEmptyPtr(cluster) + r.Account = nonEmptyPtr(account) + r.User = user + r.Partition = nonEmptyPtr(partition) + _, err := c.CreateAssoc(ctx, r) + return err +} + +// DeleteAssoc implements AssocInterface using a filter param set. +func (c *SlurmClient) DeleteAssoc(ctx context.Context, assoc api.V0044Assoc) error { + params := &api.SlurmdbV0044DeleteAssociationParams{ + Account: assoc.Account, + User: nonEmptyPtr(assoc.User), + Cluster: assoc.Cluster, + Partition: assoc.Partition, + } + res, err := c.SlurmdbV0044DeleteAssociationWithResponse(ctx, params) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetAssoc implements AssocInterface. There is no get-by-key endpoint, so the +// composite key is decomposed into server-side filter params to avoid listing +// every association. +func (c *SlurmClient) GetAssoc(ctx context.Context, key string) (*types.V0044Assoc, error) { + cluster, account, user, partition := parseAssocKey(key) + params := &api.SlurmdbV0044GetAssociationsParams{ + Account: nonEmptyPtr(account), + Cluster: nonEmptyPtr(cluster), + User: nonEmptyPtr(user), + Partition: nonEmptyPtr(partition), + } + res, err := c.SlurmdbV0044GetAssociationsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + for _, item := range res.JSON200.Associations { + out := &types.V0044Assoc{} + utils.RemarshalOrDie(item, out) + if string(out.GetKey()) == key { + return out, nil + } + } + return nil, errors.New(http.StatusText(http.StatusNotFound)) +} + +// ListAssoc implements AssocInterface. +func (c *SlurmClient) ListAssoc(ctx context.Context) (*types.V0044AssocList, error) { + params := &api.SlurmdbV0044GetAssociationsParams{} + res, err := c.SlurmdbV0044GetAssociationsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0044AssocList{ + Items: make([]types.V0044Assoc, len(res.JSON200.Associations)), + } + for i, item := range res.JSON200.Associations { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0044/association_test.go b/pkg/client/api/v0044/association_test.go new file mode 100644 index 0000000..9f3d060 --- /dev/null +++ b/pkg/client/api/v0044/association_test.go @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0044 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0044/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0044/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestAssocInterface_Compiles(t *testing.T) { + var _ AssocInterface = &SlurmClient{} +} + +func TestSlurmClient_ListAssoc(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0044AssocList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0044AssocList{Items: make([]types.V0044Assoc, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0044GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAssociationsResponse, error) { + return &api.SlurmdbV0044GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0044OpenapiAssocsResp{ + Associations: api.V0044AssocList{ + {User: "alice", Account: ptr.To("research")}, + }, + }, + }, nil + }, + }). + Build(), + want: &types.V0044AssocList{Items: []types.V0044Assoc{ + {V0044Assoc: api.V0044Assoc{User: "alice", Account: ptr.To("research")}}, + }}, + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0044GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAssociationsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListAssoc(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListAssoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListAssoc() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateAssoc(t *testing.T) { + tests := []struct { + name string + req any + wantKey string + wantErr bool + }{ + { + name: "returns composite key from request", + req: api.V0044Assoc{Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + wantKey: "linux/research/alice/", + wantErr: false, + }, + {name: "invalid type provided", req: api.V0044Account{Name: "research"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + gotKey, err := c.CreateAssoc(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateAssoc() error = %v, wantErr %v", err, tt.wantErr) + } + if gotKey != tt.wantKey { + t.Errorf("SlurmClient.CreateAssoc() key = %q, want %q", gotKey, tt.wantKey) + } + }) + } +} + +func TestSlurmClient_UpdateAssoc(t *testing.T) { + var gotBody api.V0044OpenapiAssocsResp + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044PostAssociationsWithResponse: func(ctx context.Context, body api.V0044OpenapiAssocsResp, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044PostAssociationsResponse, error) { + gotBody = body + return &api.SlurmdbV0044PostAssociationsResponse{HTTPResponse: &fake.HttpSuccess}, nil + }, + }). + Build()} + + // req identity fields deliberately differ from key; key must win. + req := api.V0044Assoc{Cluster: ptr.To("other"), Account: ptr.To("other"), User: "bob"} + if err := c.UpdateAssoc(context.Background(), "linux/research/alice/", req); err != nil { + t.Fatalf("SlurmClient.UpdateAssoc() error = %v", err) + } + if len(gotBody.Associations) != 1 { + t.Fatalf("SlurmClient.UpdateAssoc() posted %d associations, want 1", len(gotBody.Associations)) + } + got := gotBody.Associations[0] + if ptr.Deref(got.Cluster, "") != "linux" || ptr.Deref(got.Account, "") != "research" || got.User != "alice" || got.Partition != nil { + t.Errorf("SlurmClient.UpdateAssoc() pinned identity = %+v, want cluster=linux account=research user=alice partition=nil", got) + } + + // invalid type provided + if err := c.UpdateAssoc(context.Background(), "linux/research/alice/", api.V0044Account{Name: "x"}); err == nil { + t.Errorf("SlurmClient.UpdateAssoc() expected error for invalid req type") + } +} + +func TestSlurmClient_GetAssoc(t *testing.T) { + matching := func() api.ClientWithResponsesInterface { + return fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0044GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAssociationsResponse, error) { + return &api.SlurmdbV0044GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0044OpenapiAssocsResp{ + Associations: api.V0044AssocList{ + {Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + }, + }, + }, nil + }, + }). + Build() + } + tests := []struct { + name string + key string + client api.ClientWithResponsesInterface + want *types.V0044Assoc + wantErr bool + }{ + { + name: "found by key", + key: "linux/research/alice/", + client: matching(), + want: &types.V0044Assoc{V0044Assoc: api.V0044Assoc{Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}}, + }, + { + name: "not found - empty result", + key: "linux/research/bob/", + client: fake.NewFakeClient(), + wantErr: true, + }, + { + name: "not found - no key match", + key: "linux/research/bob/", + client: matching(), + wantErr: true, + }, + { + name: "HTTP error", + key: "linux/research/alice/", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0044GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAssociationsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetAssoc(context.Background(), tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetAssoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetAssoc() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_GetAssoc_FiltersServerSide(t *testing.T) { + var gotParams *api.SlurmdbV0044GetAssociationsParams + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0044GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetAssociationsResponse, error) { + gotParams = params + return &api.SlurmdbV0044GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0044OpenapiAssocsResp{ + Associations: api.V0044AssocList{ + {Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + }, + }, + }, nil + }, + }). + Build()} + + if _, err := c.GetAssoc(context.Background(), "linux/research/alice/"); err != nil { + t.Fatalf("SlurmClient.GetAssoc() error = %v", err) + } + if gotParams == nil { + t.Fatal("SlurmClient.GetAssoc() did not call the associations endpoint") + } + if ptr.Deref(gotParams.Cluster, "") != "linux" || ptr.Deref(gotParams.Account, "") != "research" || ptr.Deref(gotParams.User, "") != "alice" { + t.Errorf("SlurmClient.GetAssoc() filter params = %+v, want cluster=linux account=research user=alice", gotParams) + } + if gotParams.Partition != nil { + t.Errorf("SlurmClient.GetAssoc() partition param = %v, want nil", gotParams.Partition) + } +} + +func TestSlurmClient_DeleteAssoc(t *testing.T) { + tests := []struct { + name string + assoc api.V0044Assoc + wantUserParam *string + }{ + { + name: "with user", + assoc: api.V0044Assoc{Account: ptr.To("research"), User: "alice"}, + wantUserParam: ptr.To("alice"), + }, + { + name: "empty user omitted", + assoc: api.V0044Assoc{Account: ptr.To("research")}, + wantUserParam: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var gotParams *api.SlurmdbV0044DeleteAssociationParams + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044DeleteAssociationWithResponse: func(ctx context.Context, params *api.SlurmdbV0044DeleteAssociationParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044DeleteAssociationResponse, error) { + gotParams = params + return &api.SlurmdbV0044DeleteAssociationResponse{HTTPResponse: &fake.HttpSuccess, JSON200: &api.V0044OpenapiAssocsRemovedResp{}}, nil + }, + }). + Build()} + if err := c.DeleteAssoc(context.Background(), tt.assoc); err != nil { + t.Fatalf("SlurmClient.DeleteAssoc() error = %v", err) + } + if !reflect.DeepEqual(gotParams.User, tt.wantUserParam) { + t.Errorf("SlurmClient.DeleteAssoc() User param = %v, want %v", ptr.Deref(gotParams.User, ""), ptr.Deref(tt.wantUserParam, "")) + } + }) + } +} diff --git a/pkg/client/api/v0044/user.go b/pkg/client/api/v0044/user.go new file mode 100644 index 0000000..11daf3a --- /dev/null +++ b/pkg/client/api/v0044/user.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0044 + +import ( + "context" + "errors" + "net/http" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type UserInterface interface { + CreateUser(ctx context.Context, req any) (string, error) + UpdateUser(ctx context.Context, name string, req any) error + DeleteUser(ctx context.Context, name string) error + GetUser(ctx context.Context, name string) (*types.V0044User, error) + ListUser(ctx context.Context) (*types.V0044UserList, error) +} + +var _ UserInterface = &SlurmClient{} + +// CreateUser implements UserInterface. +func (c *SlurmClient) CreateUser(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0044User) + if !ok { + return "", errors.New("expected req to be api.V0044User") + } + body := api.SlurmdbV0044PostUsersJSONRequestBody{ + Users: api.V0044UserList{r}, + } + res, err := c.SlurmdbV0044PostUsersWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + return r.Name, nil +} + +// UpdateUser implements UserInterface. POST is upsert in slurmdbd. +func (c *SlurmClient) UpdateUser(ctx context.Context, name string, req any) error { + r, ok := req.(api.V0044User) + if !ok { + return errors.New("expected req to be api.V0044User") + } + r.Name = name + _, err := c.CreateUser(ctx, r) + return err +} + +// DeleteUser implements UserInterface. +func (c *SlurmClient) DeleteUser(ctx context.Context, name string) error { + res, err := c.SlurmdbV0044DeleteUserWithResponse(ctx, name) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetUser implements UserInterface. +func (c *SlurmClient) GetUser(ctx context.Context, name string) (*types.V0044User, error) { + params := &api.SlurmdbV0044GetUserParams{} + res, err := c.SlurmdbV0044GetUserWithResponse(ctx, name, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil || len(res.JSON200.Users) == 0 { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + out := &types.V0044User{} + utils.RemarshalOrDie(res.JSON200.Users[0], out) + return out, nil +} + +// ListUser implements UserInterface. +func (c *SlurmClient) ListUser(ctx context.Context) (*types.V0044UserList, error) { + params := &api.SlurmdbV0044GetUsersParams{} + res, err := c.SlurmdbV0044GetUsersWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0044UserList{ + Items: make([]types.V0044User, len(res.JSON200.Users)), + } + for i, item := range res.JSON200.Users { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0044/user_test.go b/pkg/client/api/v0044/user_test.go new file mode 100644 index 0000000..abd1e3b --- /dev/null +++ b/pkg/client/api/v0044/user_test.go @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0044 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0044/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0044/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestUserInterface_Compiles(t *testing.T) { + var _ UserInterface = &SlurmClient{} +} + +func TestSlurmClient_GetUser(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0044User + wantErr bool + }{ + { + name: "Not Found", + client: fake.NewFakeClient(), + want: nil, + wantErr: true, + }, + { + name: "Found", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetUserWithResponse: func(ctx context.Context, name string, params *api.SlurmdbV0044GetUserParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetUserResponse, error) { + return &api.SlurmdbV0044GetUserResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0044OpenapiUsersResp{ + Users: api.V0044UserList{{Name: "alice"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0044User{V0044User: api.V0044User{Name: "alice"}}, + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetUserWithResponse: func(ctx context.Context, name string, params *api.SlurmdbV0044GetUserParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetUserResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetUser(context.Background(), "alice") + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_ListUser(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0044UserList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0044UserList{Items: make([]types.V0044User, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0044GetUsersWithResponse: func(ctx context.Context, params *api.SlurmdbV0044GetUsersParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0044GetUsersResponse, error) { + return &api.SlurmdbV0044GetUsersResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0044OpenapiUsersResp{ + Users: api.V0044UserList{{Name: "alice"}, {Name: "bob"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0044UserList{Items: []types.V0044User{ + {V0044User: api.V0044User{Name: "alice"}}, + {V0044User: api.V0044User{Name: "bob"}}, + }}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListUser(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateUser(t *testing.T) { + tests := []struct { + name string + req any + want string + wantErr bool + }{ + {name: "default", req: api.V0044User{Name: "alice"}, want: "alice", wantErr: false}, + {name: "invalid type provided", req: api.V0044Account{Name: "alice"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + got, err := c.CreateUser(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SlurmClient.CreateUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_DeleteUser(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + if err := c.DeleteUser(context.Background(), "alice"); err != nil { + t.Errorf("SlurmClient.DeleteUser() error = %v, wantErr false", err) + } +} diff --git a/pkg/client/api/v0044/v0044.go b/pkg/client/api/v0044/v0044.go index 194eeab..a0c56ee 100644 --- a/pkg/client/api/v0044/v0044.go +++ b/pkg/client/api/v0044/v0044.go @@ -24,6 +24,8 @@ const ( type ClientInterface interface { api.ClientWithResponsesInterface + AccountInterface + AssocInterface ControllerPingInfoInterface JobInfoInterface NodeInterface @@ -32,6 +34,7 @@ type ClientInterface interface { ReconfigureInterface ReservationInterface StatsInterface + UserInterface } type SlurmClient struct { diff --git a/pkg/client/api/v0045/account.go b/pkg/client/api/v0045/account.go new file mode 100644 index 0000000..472e33e --- /dev/null +++ b/pkg/client/api/v0045/account.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0045 + +import ( + "context" + "errors" + "net/http" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type AccountInterface interface { + CreateAccount(ctx context.Context, req any) (string, error) + UpdateAccount(ctx context.Context, name string, req any) error + DeleteAccount(ctx context.Context, name string) error + GetAccount(ctx context.Context, name string) (*types.V0045Account, error) + ListAccount(ctx context.Context) (*types.V0045AccountList, error) +} + +var _ AccountInterface = &SlurmClient{} + +// CreateAccount implements AccountInterface. +func (c *SlurmClient) CreateAccount(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0045Account) + if !ok { + return "", errors.New("expected req to be api.V0045Account") + } + body := api.SlurmdbV0045PostAccountsJSONRequestBody{ + Accounts: api.V0045AccountList{r}, + } + res, err := c.SlurmdbV0045PostAccountsWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + return r.Name, nil +} + +// UpdateAccount implements AccountInterface. POST is upsert in slurmdbd. +func (c *SlurmClient) UpdateAccount(ctx context.Context, name string, req any) error { + r, ok := req.(api.V0045Account) + if !ok { + return errors.New("expected req to be api.V0045Account") + } + r.Name = name + _, err := c.CreateAccount(ctx, r) + return err +} + +// DeleteAccount implements AccountInterface. +func (c *SlurmClient) DeleteAccount(ctx context.Context, name string) error { + res, err := c.SlurmdbV0045DeleteAccountWithResponse(ctx, name) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetAccount implements AccountInterface. +func (c *SlurmClient) GetAccount(ctx context.Context, name string) (*types.V0045Account, error) { + params := &api.SlurmdbV0045GetAccountParams{} + res, err := c.SlurmdbV0045GetAccountWithResponse(ctx, name, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil || len(res.JSON200.Accounts) == 0 { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + out := &types.V0045Account{} + utils.RemarshalOrDie(res.JSON200.Accounts[0], out) + return out, nil +} + +// ListAccount implements AccountInterface. +func (c *SlurmClient) ListAccount(ctx context.Context) (*types.V0045AccountList, error) { + params := &api.SlurmdbV0045GetAccountsParams{} + res, err := c.SlurmdbV0045GetAccountsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0045AccountList{ + Items: make([]types.V0045Account, len(res.JSON200.Accounts)), + } + for i, item := range res.JSON200.Accounts { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0045/account_test.go b/pkg/client/api/v0045/account_test.go new file mode 100644 index 0000000..a634fb8 --- /dev/null +++ b/pkg/client/api/v0045/account_test.go @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0045 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0045/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0045/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestAccountInterface_Compiles(t *testing.T) { + var _ AccountInterface = &SlurmClient{} +} + +func TestSlurmClient_GetAccount(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0045Account + wantErr bool + }{ + { + name: "Not Found", + client: fake.NewFakeClient(), + want: nil, + wantErr: true, + }, + { + name: "Found", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0045GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAccountResponse, error) { + return &api.SlurmdbV0045GetAccountResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0045OpenapiAccountsResp{ + Accounts: api.V0045AccountList{{Name: "research"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0045Account{V0045Account: api.V0045Account{Name: "research"}}, + wantErr: false, + }, + { + name: "HTTP Status != 200", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0045GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAccountResponse, error) { + return &api.SlurmdbV0045GetAccountResponse{ + HTTPResponse: &http.Response{ + Status: http.StatusText(http.StatusInternalServerError), + StatusCode: http.StatusInternalServerError, + }, + JSONDefault: &api.V0045OpenapiAccountsResp{ + Errors: &[]api.V0045OpenapiError{{Error: ptr.To("error 1")}}, + }, + }, nil + }, + }). + Build(), + want: nil, + wantErr: true, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAccountWithResponse: func(ctx context.Context, accountName string, params *api.SlurmdbV0045GetAccountParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAccountResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetAccount(context.Background(), "research") + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_ListAccount(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0045AccountList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0045AccountList{Items: make([]types.V0045Account, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAccountsWithResponse: func(ctx context.Context, params *api.SlurmdbV0045GetAccountsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAccountsResponse, error) { + return &api.SlurmdbV0045GetAccountsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0045OpenapiAccountsResp{ + Accounts: api.V0045AccountList{{Name: "a"}, {Name: "b"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0045AccountList{Items: []types.V0045Account{ + {V0045Account: api.V0045Account{Name: "a"}}, + {V0045Account: api.V0045Account{Name: "b"}}, + }}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListAccount(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateAccount(t *testing.T) { + tests := []struct { + name string + req any + want string + wantErr bool + }{ + {name: "default", req: api.V0045Account{Name: "research"}, want: "research", wantErr: false}, + {name: "invalid type provided", req: api.V0045User{Name: "research"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + got, err := c.CreateAccount(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateAccount() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SlurmClient.CreateAccount() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_DeleteAccount(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + if err := c.DeleteAccount(context.Background(), "research"); err != nil { + t.Errorf("SlurmClient.DeleteAccount() error = %v, wantErr false", err) + } +} diff --git a/pkg/client/api/v0045/association.go b/pkg/client/api/v0045/association.go new file mode 100644 index 0000000..9ab6033 --- /dev/null +++ b/pkg/client/api/v0045/association.go @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0045 + +import ( + "context" + "errors" + "net/http" + "strings" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type AssocInterface interface { + CreateAssoc(ctx context.Context, req any) (string, error) + UpdateAssoc(ctx context.Context, key string, req any) error + DeleteAssoc(ctx context.Context, assoc api.V0045Assoc) error + GetAssoc(ctx context.Context, key string) (*types.V0045Assoc, error) + ListAssoc(ctx context.Context) (*types.V0045AssocList, error) +} + +var _ AssocInterface = &SlurmClient{} + +// parseAssocKey splits a "cluster/account/user/partition" association key. +func parseAssocKey(key string) (cluster, account, user, partition string) { + parts := strings.SplitN(key, "/", 4) + for len(parts) < 4 { + parts = append(parts, "") + } + return parts[0], parts[1], parts[2], parts[3] +} + +func nonEmptyPtr(s string) *string { + if s == "" { + return nil + } + return ptr.To(s) +} + +// CreateAssoc implements AssocInterface (POST is upsert). It returns the +// composite key derived from the request so callers can refresh by identity. +func (c *SlurmClient) CreateAssoc(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0045Assoc) + if !ok { + return "", errors.New("expected req to be api.V0045Assoc") + } + body := api.SlurmdbV0045PostAssociationsJSONRequestBody{ + Associations: api.V0045AssocList{r}, + } + res, err := c.SlurmdbV0045PostAssociationsWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + assoc := &types.V0045Assoc{V0045Assoc: r} + return string(assoc.GetKey()), nil +} + +// UpdateAssoc implements AssocInterface. POST is upsert in slurmdbd; the +// association identity is pinned from key so the update targets the intended +// association regardless of the identity fields set on req. +func (c *SlurmClient) UpdateAssoc(ctx context.Context, key string, req any) error { + r, ok := req.(api.V0045Assoc) + if !ok { + return errors.New("expected req to be api.V0045Assoc") + } + cluster, account, user, partition := parseAssocKey(key) + r.Cluster = nonEmptyPtr(cluster) + r.Account = nonEmptyPtr(account) + r.User = user + r.Partition = nonEmptyPtr(partition) + _, err := c.CreateAssoc(ctx, r) + return err +} + +// DeleteAssoc implements AssocInterface using a filter param set. +func (c *SlurmClient) DeleteAssoc(ctx context.Context, assoc api.V0045Assoc) error { + params := &api.SlurmdbV0045DeleteAssociationParams{ + Account: assoc.Account, + User: nonEmptyPtr(assoc.User), + Cluster: assoc.Cluster, + Partition: assoc.Partition, + } + res, err := c.SlurmdbV0045DeleteAssociationWithResponse(ctx, params) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetAssoc implements AssocInterface. There is no get-by-key endpoint, so the +// composite key is decomposed into server-side filter params to avoid listing +// every association. +func (c *SlurmClient) GetAssoc(ctx context.Context, key string) (*types.V0045Assoc, error) { + cluster, account, user, partition := parseAssocKey(key) + params := &api.SlurmdbV0045GetAssociationsParams{ + Account: nonEmptyPtr(account), + Cluster: nonEmptyPtr(cluster), + User: nonEmptyPtr(user), + Partition: nonEmptyPtr(partition), + } + res, err := c.SlurmdbV0045GetAssociationsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + for _, item := range res.JSON200.Associations { + out := &types.V0045Assoc{} + utils.RemarshalOrDie(item, out) + if string(out.GetKey()) == key { + return out, nil + } + } + return nil, errors.New(http.StatusText(http.StatusNotFound)) +} + +// ListAssoc implements AssocInterface. +func (c *SlurmClient) ListAssoc(ctx context.Context) (*types.V0045AssocList, error) { + params := &api.SlurmdbV0045GetAssociationsParams{} + res, err := c.SlurmdbV0045GetAssociationsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0045AssocList{ + Items: make([]types.V0045Assoc, len(res.JSON200.Associations)), + } + for i, item := range res.JSON200.Associations { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0045/association_test.go b/pkg/client/api/v0045/association_test.go new file mode 100644 index 0000000..1a8617b --- /dev/null +++ b/pkg/client/api/v0045/association_test.go @@ -0,0 +1,284 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0045 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0045/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0045/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestAssocInterface_Compiles(t *testing.T) { + var _ AssocInterface = &SlurmClient{} +} + +func TestSlurmClient_ListAssoc(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0045AssocList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0045AssocList{Items: make([]types.V0045Assoc, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0045GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAssociationsResponse, error) { + return &api.SlurmdbV0045GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0045OpenapiAssocsResp{ + Associations: api.V0045AssocList{ + {User: "alice", Account: ptr.To("research")}, + }, + }, + }, nil + }, + }). + Build(), + want: &types.V0045AssocList{Items: []types.V0045Assoc{ + {V0045Assoc: api.V0045Assoc{User: "alice", Account: ptr.To("research")}}, + }}, + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0045GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAssociationsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListAssoc(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListAssoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListAssoc() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateAssoc(t *testing.T) { + tests := []struct { + name string + req any + wantKey string + wantErr bool + }{ + { + name: "returns composite key from request", + req: api.V0045Assoc{Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + wantKey: "linux/research/alice/", + wantErr: false, + }, + {name: "invalid type provided", req: api.V0045Account{Name: "research"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + gotKey, err := c.CreateAssoc(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateAssoc() error = %v, wantErr %v", err, tt.wantErr) + } + if gotKey != tt.wantKey { + t.Errorf("SlurmClient.CreateAssoc() key = %q, want %q", gotKey, tt.wantKey) + } + }) + } +} + +func TestSlurmClient_UpdateAssoc(t *testing.T) { + var gotBody api.V0045OpenapiAssocsResp + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045PostAssociationsWithResponse: func(ctx context.Context, body api.V0045OpenapiAssocsResp, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045PostAssociationsResponse, error) { + gotBody = body + return &api.SlurmdbV0045PostAssociationsResponse{HTTPResponse: &fake.HttpSuccess}, nil + }, + }). + Build()} + + // req identity fields deliberately differ from key; key must win. + req := api.V0045Assoc{Cluster: ptr.To("other"), Account: ptr.To("other"), User: "bob"} + if err := c.UpdateAssoc(context.Background(), "linux/research/alice/", req); err != nil { + t.Fatalf("SlurmClient.UpdateAssoc() error = %v", err) + } + if len(gotBody.Associations) != 1 { + t.Fatalf("SlurmClient.UpdateAssoc() posted %d associations, want 1", len(gotBody.Associations)) + } + got := gotBody.Associations[0] + if ptr.Deref(got.Cluster, "") != "linux" || ptr.Deref(got.Account, "") != "research" || got.User != "alice" || got.Partition != nil { + t.Errorf("SlurmClient.UpdateAssoc() pinned identity = %+v, want cluster=linux account=research user=alice partition=nil", got) + } + + // invalid type provided + if err := c.UpdateAssoc(context.Background(), "linux/research/alice/", api.V0045Account{Name: "x"}); err == nil { + t.Errorf("SlurmClient.UpdateAssoc() expected error for invalid req type") + } +} + +func TestSlurmClient_GetAssoc(t *testing.T) { + matching := func() api.ClientWithResponsesInterface { + return fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0045GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAssociationsResponse, error) { + return &api.SlurmdbV0045GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0045OpenapiAssocsResp{ + Associations: api.V0045AssocList{ + {Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + }, + }, + }, nil + }, + }). + Build() + } + tests := []struct { + name string + key string + client api.ClientWithResponsesInterface + want *types.V0045Assoc + wantErr bool + }{ + { + name: "found by key", + key: "linux/research/alice/", + client: matching(), + want: &types.V0045Assoc{V0045Assoc: api.V0045Assoc{Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}}, + }, + { + name: "not found - empty result", + key: "linux/research/bob/", + client: fake.NewFakeClient(), + wantErr: true, + }, + { + name: "not found - no key match", + key: "linux/research/bob/", + client: matching(), + wantErr: true, + }, + { + name: "HTTP error", + key: "linux/research/alice/", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0045GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAssociationsResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetAssoc(context.Background(), tt.key) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetAssoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetAssoc() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_GetAssoc_FiltersServerSide(t *testing.T) { + var gotParams *api.SlurmdbV0045GetAssociationsParams + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetAssociationsWithResponse: func(ctx context.Context, params *api.SlurmdbV0045GetAssociationsParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetAssociationsResponse, error) { + gotParams = params + return &api.SlurmdbV0045GetAssociationsResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0045OpenapiAssocsResp{ + Associations: api.V0045AssocList{ + {Cluster: ptr.To("linux"), Account: ptr.To("research"), User: "alice"}, + }, + }, + }, nil + }, + }). + Build()} + + if _, err := c.GetAssoc(context.Background(), "linux/research/alice/"); err != nil { + t.Fatalf("SlurmClient.GetAssoc() error = %v", err) + } + if gotParams == nil { + t.Fatal("SlurmClient.GetAssoc() did not call the associations endpoint") + } + if ptr.Deref(gotParams.Cluster, "") != "linux" || ptr.Deref(gotParams.Account, "") != "research" || ptr.Deref(gotParams.User, "") != "alice" { + t.Errorf("SlurmClient.GetAssoc() filter params = %+v, want cluster=linux account=research user=alice", gotParams) + } + if gotParams.Partition != nil { + t.Errorf("SlurmClient.GetAssoc() partition param = %v, want nil", gotParams.Partition) + } +} + +func TestSlurmClient_DeleteAssoc(t *testing.T) { + tests := []struct { + name string + assoc api.V0045Assoc + wantUserParam *string + }{ + { + name: "with user", + assoc: api.V0045Assoc{Account: ptr.To("research"), User: "alice"}, + wantUserParam: ptr.To("alice"), + }, + { + name: "empty user omitted", + assoc: api.V0045Assoc{Account: ptr.To("research")}, + wantUserParam: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var gotParams *api.SlurmdbV0045DeleteAssociationParams + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045DeleteAssociationWithResponse: func(ctx context.Context, params *api.SlurmdbV0045DeleteAssociationParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045DeleteAssociationResponse, error) { + gotParams = params + return &api.SlurmdbV0045DeleteAssociationResponse{HTTPResponse: &fake.HttpSuccess, JSON200: &api.V0045OpenapiAssocsRemovedResp{}}, nil + }, + }). + Build()} + if err := c.DeleteAssoc(context.Background(), tt.assoc); err != nil { + t.Fatalf("SlurmClient.DeleteAssoc() error = %v", err) + } + if !reflect.DeepEqual(gotParams.User, tt.wantUserParam) { + t.Errorf("SlurmClient.DeleteAssoc() User param = %v, want %v", ptr.Deref(gotParams.User, ""), ptr.Deref(tt.wantUserParam, "")) + } + }) + } +} diff --git a/pkg/client/api/v0045/user.go b/pkg/client/api/v0045/user.go new file mode 100644 index 0000000..c579eed --- /dev/null +++ b/pkg/client/api/v0045/user.go @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0045 + +import ( + "context" + "errors" + "net/http" + + utilerrors "k8s.io/apimachinery/pkg/util/errors" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/types" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +type UserInterface interface { + CreateUser(ctx context.Context, req any) (string, error) + UpdateUser(ctx context.Context, name string, req any) error + DeleteUser(ctx context.Context, name string) error + GetUser(ctx context.Context, name string) (*types.V0045User, error) + ListUser(ctx context.Context) (*types.V0045UserList, error) +} + +var _ UserInterface = &SlurmClient{} + +// CreateUser implements UserInterface. +func (c *SlurmClient) CreateUser(ctx context.Context, req any) (string, error) { + r, ok := req.(api.V0045User) + if !ok { + return "", errors.New("expected req to be api.V0045User") + } + body := api.SlurmdbV0045PostUsersJSONRequestBody{ + Users: api.V0045UserList{r}, + } + res, err := c.SlurmdbV0045PostUsersWithResponse(ctx, body) + if err != nil { + return "", err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return "", utilerrors.NewAggregate(errs) + } + return r.Name, nil +} + +// UpdateUser implements UserInterface. POST is upsert in slurmdbd. +func (c *SlurmClient) UpdateUser(ctx context.Context, name string, req any) error { + r, ok := req.(api.V0045User) + if !ok { + return errors.New("expected req to be api.V0045User") + } + r.Name = name + _, err := c.CreateUser(ctx, r) + return err +} + +// DeleteUser implements UserInterface. +func (c *SlurmClient) DeleteUser(ctx context.Context, name string) error { + res, err := c.SlurmdbV0045DeleteUserWithResponse(ctx, name) + if err != nil { + return err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return utilerrors.NewAggregate(errs) + } + return nil +} + +// GetUser implements UserInterface. +func (c *SlurmClient) GetUser(ctx context.Context, name string) (*types.V0045User, error) { + params := &api.SlurmdbV0045GetUserParams{} + res, err := c.SlurmdbV0045GetUserWithResponse(ctx, name, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + if res.JSON200 == nil || len(res.JSON200.Users) == 0 { + return nil, errors.New(http.StatusText(http.StatusNotFound)) + } + out := &types.V0045User{} + utils.RemarshalOrDie(res.JSON200.Users[0], out) + return out, nil +} + +// ListUser implements UserInterface. +func (c *SlurmClient) ListUser(ctx context.Context) (*types.V0045UserList, error) { + params := &api.SlurmdbV0045GetUsersParams{} + res, err := c.SlurmdbV0045GetUsersWithResponse(ctx, params) + if err != nil { + return nil, err + } + if res.StatusCode() != 200 { + errs := []error{errors.New(http.StatusText(res.StatusCode()))} + if res.JSONDefault != nil { + errs = append(errs, getOpenapiErrors(res.JSONDefault.Errors)...) + } + return nil, utilerrors.NewAggregate(errs) + } + list := &types.V0045UserList{ + Items: make([]types.V0045User, len(res.JSON200.Users)), + } + for i, item := range res.JSON200.Users { + utils.RemarshalOrDie(item, &list.Items[i]) + } + return list, nil +} diff --git a/pkg/client/api/v0045/user_test.go b/pkg/client/api/v0045/user_test.go new file mode 100644 index 0000000..fa4e265 --- /dev/null +++ b/pkg/client/api/v0045/user_test.go @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package v0045 + +import ( + "context" + "errors" + "net/http" + "reflect" + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0045/fake" + "github.com/SlinkyProject/slurm-client/pkg/client/api/v0045/interceptor" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestUserInterface_Compiles(t *testing.T) { + var _ UserInterface = &SlurmClient{} +} + +func TestSlurmClient_GetUser(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0045User + wantErr bool + }{ + { + name: "Not Found", + client: fake.NewFakeClient(), + want: nil, + wantErr: true, + }, + { + name: "Found", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetUserWithResponse: func(ctx context.Context, name string, params *api.SlurmdbV0045GetUserParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetUserResponse, error) { + return &api.SlurmdbV0045GetUserResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0045OpenapiUsersResp{ + Users: api.V0045UserList{{Name: "alice"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0045User{V0045User: api.V0045User{Name: "alice"}}, + wantErr: false, + }, + { + name: "HTTP Error", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetUserWithResponse: func(ctx context.Context, name string, params *api.SlurmdbV0045GetUserParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetUserResponse, error) { + return nil, errors.New(http.StatusText(http.StatusBadGateway)) + }, + }). + Build(), + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.GetUser(context.Background(), "alice") + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.GetUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.GetUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_ListUser(t *testing.T) { + tests := []struct { + name string + client api.ClientWithResponsesInterface + want *types.V0045UserList + wantErr bool + }{ + { + name: "Empty list", + client: fake.NewFakeClient(), + want: &types.V0045UserList{Items: make([]types.V0045User, 0)}, + wantErr: false, + }, + { + name: "Non-empty list", + client: fake.NewFakeClientBuilder(). + WithInterceptorFuncs(interceptor.Funcs{ + SlurmdbV0045GetUsersWithResponse: func(ctx context.Context, params *api.SlurmdbV0045GetUsersParams, reqEditors ...api.RequestEditorFn) (*api.SlurmdbV0045GetUsersResponse, error) { + return &api.SlurmdbV0045GetUsersResponse{ + HTTPResponse: &fake.HttpSuccess, + JSON200: &api.V0045OpenapiUsersResp{ + Users: api.V0045UserList{{Name: "alice"}, {Name: "bob"}}, + }, + }, nil + }, + }). + Build(), + want: &types.V0045UserList{Items: []types.V0045User{ + {V0045User: api.V0045User{Name: "alice"}}, + {V0045User: api.V0045User{Name: "bob"}}, + }}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: tt.client} + got, err := c.ListUser(context.Background()) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.ListUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SlurmClient.ListUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_CreateUser(t *testing.T) { + tests := []struct { + name string + req any + want string + wantErr bool + }{ + {name: "default", req: api.V0045User{Name: "alice"}, want: "alice", wantErr: false}, + {name: "invalid type provided", req: api.V0045Account{Name: "alice"}, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + got, err := c.CreateUser(context.Background(), tt.req) + if (err != nil) != tt.wantErr { + t.Errorf("SlurmClient.CreateUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("SlurmClient.CreateUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSlurmClient_DeleteUser(t *testing.T) { + c := &SlurmClient{ClientWithResponsesInterface: fake.NewFakeClient()} + if err := c.DeleteUser(context.Background(), "alice"); err != nil { + t.Errorf("SlurmClient.DeleteUser() error = %v, wantErr false", err) + } +} diff --git a/pkg/client/api/v0045/v0045.go b/pkg/client/api/v0045/v0045.go index 1a0774c..51d5e1a 100644 --- a/pkg/client/api/v0045/v0045.go +++ b/pkg/client/api/v0045/v0045.go @@ -24,6 +24,8 @@ const ( type ClientInterface interface { api.ClientWithResponsesInterface + AccountInterface + AssocInterface ControllerPingInfoInterface JobInfoInterface NodeInterface @@ -32,6 +34,7 @@ type ClientInterface interface { ReconfigureInterface ReservationInterface StatsInterface + UserInterface } type SlurmClient struct { diff --git a/pkg/client/client.go b/pkg/client/client.go index 4f95d61..cfe120b 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -163,6 +163,21 @@ func (c *client) Create( jobId, err = c.v0042Client.CreateJobInfo(ctx, req) key = object.ObjectKey(fmt.Sprintf("%d", ptr.Deref(jobId, 0))) + case *types.V0042Account: + var name string + name, err = c.v0042Client.CreateAccount(ctx, req) + key = object.ObjectKey(name) + + case *types.V0042User: + var name string + name, err = c.v0042Client.CreateUser(ctx, req) + key = object.ObjectKey(name) + + case *types.V0042Assoc: + var assocKey string + assocKey, err = c.v0042Client.CreateAssoc(ctx, req) + key = object.ObjectKey(assocKey) + ///////////////////////////////////////////////////////////////////////////////// case *types.V0043JobInfo: @@ -170,6 +185,21 @@ func (c *client) Create( jobId, err = c.v0043Client.CreateJobInfo(ctx, req) key = object.ObjectKey(fmt.Sprintf("%d", ptr.Deref(jobId, 0))) + case *types.V0043Account: + var name string + name, err = c.v0043Client.CreateAccount(ctx, req) + key = object.ObjectKey(name) + + case *types.V0043User: + var name string + name, err = c.v0043Client.CreateUser(ctx, req) + key = object.ObjectKey(name) + + case *types.V0043Assoc: + var assocKey string + assocKey, err = c.v0043Client.CreateAssoc(ctx, req) + key = object.ObjectKey(assocKey) + ///////////////////////////////////////////////////////////////////////////////// case *types.V0044JobInfo: @@ -187,6 +217,21 @@ func (c *client) Create( nodeName, err = c.v0044Client.CreateNewNode(ctx, req) key = object.ObjectKey(ptr.Deref(nodeName, "")) + case *types.V0044Account: + var name string + name, err = c.v0044Client.CreateAccount(ctx, req) + key = object.ObjectKey(name) + + case *types.V0044User: + var name string + name, err = c.v0044Client.CreateUser(ctx, req) + key = object.ObjectKey(name) + + case *types.V0044Assoc: + var assocKey string + assocKey, err = c.v0044Client.CreateAssoc(ctx, req) + key = object.ObjectKey(assocKey) + ///////////////////////////////////////////////////////////////////////////////// case *types.V0045JobInfo: @@ -204,6 +249,21 @@ func (c *client) Create( nodeName, err = c.v0045Client.CreateNewNode(ctx, req) key = object.ObjectKey(ptr.Deref(nodeName, "")) + case *types.V0045Account: + var name string + name, err = c.v0045Client.CreateAccount(ctx, req) + key = object.ObjectKey(name) + + case *types.V0045User: + var name string + name, err = c.v0045Client.CreateUser(ctx, req) + key = object.ObjectKey(name) + + case *types.V0045Assoc: + var assocKey string + assocKey, err = c.v0045Client.CreateAssoc(ctx, req) + key = object.ObjectKey(assocKey) + ///////////////////////////////////////////////////////////////////////////////// default: @@ -229,13 +289,19 @@ func (c *client) Delete( var err error key := string(obj.GetKey()) - switch obj.(type) { + switch o := obj.(type) { ///////////////////////////////////////////////////////////////////////////////// case *types.V0042JobInfo: err = c.v0042Client.DeleteJobInfo(ctx, key) case *types.V0042Node: err = c.v0042Client.DeleteNode(ctx, key) + case *types.V0042Account: + err = c.v0042Client.DeleteAccount(ctx, key) + case *types.V0042User: + err = c.v0042Client.DeleteUser(ctx, key) + case *types.V0042Assoc: + err = c.v0042Client.DeleteAssoc(ctx, o.V0042Assoc) ///////////////////////////////////////////////////////////////////////////////// @@ -243,6 +309,12 @@ func (c *client) Delete( err = c.v0043Client.DeleteJobInfo(ctx, key) case *types.V0043Node: err = c.v0043Client.DeleteNode(ctx, key) + case *types.V0043Account: + err = c.v0043Client.DeleteAccount(ctx, key) + case *types.V0043User: + err = c.v0043Client.DeleteUser(ctx, key) + case *types.V0043Assoc: + err = c.v0043Client.DeleteAssoc(ctx, o.V0043Assoc) ///////////////////////////////////////////////////////////////////////////////// @@ -252,6 +324,12 @@ func (c *client) Delete( err = c.v0044Client.DeleteNode(ctx, key) case *types.V0044ReservationInfo: err = c.v0044Client.DeleteReservationInfo(ctx, key) + case *types.V0044Account: + err = c.v0044Client.DeleteAccount(ctx, key) + case *types.V0044User: + err = c.v0044Client.DeleteUser(ctx, key) + case *types.V0044Assoc: + err = c.v0044Client.DeleteAssoc(ctx, o.V0044Assoc) ///////////////////////////////////////////////////////////////////////////////// @@ -261,6 +339,12 @@ func (c *client) Delete( err = c.v0045Client.DeleteNode(ctx, key) case *types.V0045ReservationInfo: err = c.v0045Client.DeleteReservationInfo(ctx, key) + case *types.V0045Account: + err = c.v0045Client.DeleteAccount(ctx, key) + case *types.V0045User: + err = c.v0045Client.DeleteUser(ctx, key) + case *types.V0045Assoc: + err = c.v0045Client.DeleteAssoc(ctx, o.V0045Assoc) ///////////////////////////////////////////////////////////////////////////////// @@ -305,6 +389,12 @@ func (c *client) Update( err = c.v0042Client.UpdateJobInfo(ctx, key, req) case *types.V0042Node: err = c.v0042Client.UpdateNode(ctx, key, req) + case *types.V0042Account: + err = c.v0042Client.UpdateAccount(ctx, key, req) + case *types.V0042User: + err = c.v0042Client.UpdateUser(ctx, key, req) + case *types.V0042Assoc: + err = c.v0042Client.UpdateAssoc(ctx, key, req) ///////////////////////////////////////////////////////////////////////////////// @@ -312,6 +402,12 @@ func (c *client) Update( err = c.v0043Client.UpdateJobInfo(ctx, key, req) case *types.V0043Node: err = c.v0043Client.UpdateNode(ctx, key, req) + case *types.V0043Account: + err = c.v0043Client.UpdateAccount(ctx, key, req) + case *types.V0043User: + err = c.v0043Client.UpdateUser(ctx, key, req) + case *types.V0043Assoc: + err = c.v0043Client.UpdateAssoc(ctx, key, req) ///////////////////////////////////////////////////////////////////////////////// @@ -321,6 +417,12 @@ func (c *client) Update( err = c.v0044Client.UpdateNode(ctx, key, req) case *types.V0044ReservationInfo: err = c.v0044Client.UpdateReservationInfo(ctx, key, req) + case *types.V0044Account: + err = c.v0044Client.UpdateAccount(ctx, key, req) + case *types.V0044User: + err = c.v0044Client.UpdateUser(ctx, key, req) + case *types.V0044Assoc: + err = c.v0044Client.UpdateAssoc(ctx, key, req) ///////////////////////////////////////////////////////////////////////////////// @@ -330,6 +432,12 @@ func (c *client) Update( err = c.v0045Client.UpdateNode(ctx, key, req) case *types.V0045ReservationInfo: err = c.v0045Client.UpdateReservationInfo(ctx, key, req) + case *types.V0045Account: + err = c.v0045Client.UpdateAccount(ctx, key, req) + case *types.V0045User: + err = c.v0045Client.UpdateUser(ctx, key, req) + case *types.V0045Assoc: + err = c.v0045Client.UpdateAssoc(ctx, key, req) ///////////////////////////////////////////////////////////////////////////////// @@ -397,6 +505,24 @@ func (c *client) Get( return err } *o = *out + case *types.V0042Account: + out, err := c.v0042Client.GetAccount(ctx, string(key)) + if err != nil { + return err + } + *o = *out + case *types.V0042User: + out, err := c.v0042Client.GetUser(ctx, string(key)) + if err != nil { + return err + } + *o = *out + case *types.V0042Assoc: + out, err := c.v0042Client.GetAssoc(ctx, string(key)) + if err != nil { + return err + } + *o = *out case *types.V0042Stats: out, err := c.v0042Client.GetStats(ctx) if err != nil { @@ -436,6 +562,24 @@ func (c *client) Get( return err } *o = *out + case *types.V0043Account: + out, err := c.v0043Client.GetAccount(ctx, string(key)) + if err != nil { + return err + } + *o = *out + case *types.V0043User: + out, err := c.v0043Client.GetUser(ctx, string(key)) + if err != nil { + return err + } + *o = *out + case *types.V0043Assoc: + out, err := c.v0043Client.GetAssoc(ctx, string(key)) + if err != nil { + return err + } + *o = *out case *types.V0043Stats: out, err := c.v0043Client.GetStats(ctx) if err != nil { @@ -487,6 +631,24 @@ func (c *client) Get( return err } *o = *out + case *types.V0044Account: + out, err := c.v0044Client.GetAccount(ctx, string(key)) + if err != nil { + return err + } + *o = *out + case *types.V0044User: + out, err := c.v0044Client.GetUser(ctx, string(key)) + if err != nil { + return err + } + *o = *out + case *types.V0044Assoc: + out, err := c.v0044Client.GetAssoc(ctx, string(key)) + if err != nil { + return err + } + *o = *out case *types.V0044Stats: out, err := c.v0044Client.GetStats(ctx) if err != nil { @@ -538,6 +700,24 @@ func (c *client) Get( return err } *o = *out + case *types.V0045Account: + out, err := c.v0045Client.GetAccount(ctx, string(key)) + if err != nil { + return err + } + *o = *out + case *types.V0045User: + out, err := c.v0045Client.GetUser(ctx, string(key)) + if err != nil { + return err + } + *o = *out + case *types.V0045Assoc: + out, err := c.v0045Client.GetAssoc(ctx, string(key)) + if err != nil { + return err + } + *o = *out case *types.V0045Stats: out, err := c.v0045Client.GetStats(ctx) if err != nil { @@ -607,6 +787,24 @@ func (c *client) List( return err } *objList = *out + case *types.V0042AccountList: + out, err := c.v0042Client.ListAccount(ctx) + if err != nil { + return err + } + *objList = *out + case *types.V0042UserList: + out, err := c.v0042Client.ListUser(ctx) + if err != nil { + return err + } + *objList = *out + case *types.V0042AssocList: + out, err := c.v0042Client.ListAssoc(ctx) + if err != nil { + return err + } + *objList = *out case *types.V0042StatsList: out, err := c.v0042Client.ListStats(ctx) if err != nil { @@ -646,6 +844,24 @@ func (c *client) List( return err } *objList = *out + case *types.V0043AccountList: + out, err := c.v0043Client.ListAccount(ctx) + if err != nil { + return err + } + *objList = *out + case *types.V0043UserList: + out, err := c.v0043Client.ListUser(ctx) + if err != nil { + return err + } + *objList = *out + case *types.V0043AssocList: + out, err := c.v0043Client.ListAssoc(ctx) + if err != nil { + return err + } + *objList = *out case *types.V0043StatsList: out, err := c.v0043Client.ListStats(ctx) if err != nil { @@ -691,6 +907,24 @@ func (c *client) List( return err } *objList = *out + case *types.V0044AccountList: + out, err := c.v0044Client.ListAccount(ctx) + if err != nil { + return err + } + *objList = *out + case *types.V0044UserList: + out, err := c.v0044Client.ListUser(ctx) + if err != nil { + return err + } + *objList = *out + case *types.V0044AssocList: + out, err := c.v0044Client.ListAssoc(ctx) + if err != nil { + return err + } + *objList = *out case *types.V0044StatsList: out, err := c.v0044Client.ListStats(ctx) if err != nil { @@ -736,6 +970,24 @@ func (c *client) List( return err } *objList = *out + case *types.V0045AccountList: + out, err := c.v0045Client.ListAccount(ctx) + if err != nil { + return err + } + *objList = *out + case *types.V0045UserList: + out, err := c.v0045Client.ListUser(ctx) + if err != nil { + return err + } + *objList = *out + case *types.V0045AssocList: + out, err := c.v0045Client.ListAssoc(ctx) + if err != nil { + return err + } + *objList = *out case *types.V0045StatsList: out, err := c.v0045Client.ListStats(ctx) if err != nil { diff --git a/pkg/client/fake/client.go b/pkg/client/fake/client.go index 349715d..df9895d 100644 --- a/pkg/client/fake/client.go +++ b/pkg/client/fake/client.go @@ -131,6 +131,15 @@ func (c *fakeClient) Get(ctx context.Context, key object.ObjectKey, obj object.O case *types.V0042PartitionInfo: cache := entry.(*types.V0042PartitionInfo) *o = *cache + case *types.V0042Account: + cache := entry.(*types.V0042Account) + *o = *cache + case *types.V0042User: + cache := entry.(*types.V0042User) + *o = *cache + case *types.V0042Assoc: + cache := entry.(*types.V0042Assoc) + *o = *cache case *types.V0042Stats: cache := entry.(*types.V0042Stats) *o = *cache @@ -149,6 +158,15 @@ func (c *fakeClient) Get(ctx context.Context, key object.ObjectKey, obj object.O case *types.V0043PartitionInfo: cache := entry.(*types.V0043PartitionInfo) *o = *cache + case *types.V0043Account: + cache := entry.(*types.V0043Account) + *o = *cache + case *types.V0043User: + cache := entry.(*types.V0043User) + *o = *cache + case *types.V0043Assoc: + cache := entry.(*types.V0043Assoc) + *o = *cache case *types.V0043Stats: cache := entry.(*types.V0043Stats) *o = *cache @@ -173,6 +191,15 @@ func (c *fakeClient) Get(ctx context.Context, key object.ObjectKey, obj object.O case *types.V0044ReservationInfo: cache := entry.(*types.V0044ReservationInfo) *o = *cache + case *types.V0044Account: + cache := entry.(*types.V0044Account) + *o = *cache + case *types.V0044User: + cache := entry.(*types.V0044User) + *o = *cache + case *types.V0044Assoc: + cache := entry.(*types.V0044Assoc) + *o = *cache case *types.V0044Stats: cache := entry.(*types.V0044Stats) *o = *cache @@ -197,6 +224,15 @@ func (c *fakeClient) Get(ctx context.Context, key object.ObjectKey, obj object.O case *types.V0045ReservationInfo: cache := entry.(*types.V0045ReservationInfo) *o = *cache + case *types.V0045Account: + cache := entry.(*types.V0045Account) + *o = *cache + case *types.V0045User: + cache := entry.(*types.V0045User) + *o = *cache + case *types.V0045Assoc: + cache := entry.(*types.V0045Assoc) + *o = *cache case *types.V0045Stats: cache := entry.(*types.V0045Stats) *o = *cache diff --git a/pkg/client/fake/fake_account_test.go b/pkg/client/fake/fake_account_test.go new file mode 100644 index 0000000..3809c0a --- /dev/null +++ b/pkg/client/fake/fake_account_test.go @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package fake + +import ( + "context" + "testing" + + apiv0042 "github.com/SlinkyProject/slurm-client/api/v0042" + apiv0043 "github.com/SlinkyProject/slurm-client/api/v0043" + apiv0044 "github.com/SlinkyProject/slurm-client/api/v0044" + apiv0045 "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/client" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestFakeClient_V0042AccountRoundTrip(t *testing.T) { + ctx := context.Background() + acct := &types.V0042Account{V0042Account: apiv0042.V0042Account{Name: "research"}} + c := NewClientBuilder().WithObjects(acct).Build() + + got := &types.V0042Account{} + if err := c.Get(ctx, object.ObjectKey("research"), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.Name != "research" { + t.Errorf("got account %q, want research", got.Name) + } + + list := &types.V0042AccountList{} + if err := c.List(ctx, list); err != nil { + t.Fatalf("List() error = %v", err) + } + if len(list.Items) != 1 { + t.Errorf("List() len = %d, want 1", len(list.Items)) + } +} + +func TestFakeClient_V0043AccountRoundTrip(t *testing.T) { + ctx := context.Background() + acct := &types.V0043Account{V0043Account: apiv0043.V0043Account{Name: "research"}} + c := NewClientBuilder().WithObjects(acct).Build() + + got := &types.V0043Account{} + if err := c.Get(ctx, object.ObjectKey("research"), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.Name != "research" { + t.Errorf("got account %q, want research", got.Name) + } + + list := &types.V0043AccountList{} + if err := c.List(ctx, list); err != nil { + t.Fatalf("List() error = %v", err) + } + if len(list.Items) != 1 { + t.Errorf("List() len = %d, want 1", len(list.Items)) + } +} + +func TestFakeClient_V0044AccountRoundTrip(t *testing.T) { + ctx := context.Background() + acct := &types.V0044Account{V0044Account: apiv0044.V0044Account{Name: "research"}} + c := NewClientBuilder().WithObjects(acct).Build() + + got := &types.V0044Account{} + if err := c.Get(ctx, object.ObjectKey("research"), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.Name != "research" { + t.Errorf("got account %q, want research", got.Name) + } + + list := &types.V0044AccountList{} + if err := c.List(ctx, list); err != nil { + t.Fatalf("List() error = %v", err) + } + if len(list.Items) != 1 { + t.Errorf("List() len = %d, want 1", len(list.Items)) + } +} + +func TestFakeClient_V0045AccountRoundTrip(t *testing.T) { + ctx := context.Background() + acct := &types.V0045Account{V0045Account: apiv0045.V0045Account{Name: "research"}} + c := NewClientBuilder().WithObjects(acct).Build() + + got := &types.V0045Account{} + if err := c.Get(ctx, object.ObjectKey("research"), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.Name != "research" { + t.Errorf("got account %q, want research", got.Name) + } + + list := &types.V0045AccountList{} + if err := c.List(ctx, list); err != nil { + t.Fatalf("List() error = %v", err) + } + if len(list.Items) != 1 { + t.Errorf("List() len = %d, want 1", len(list.Items)) + } +} diff --git a/pkg/client/fake/fake_association_test.go b/pkg/client/fake/fake_association_test.go new file mode 100644 index 0000000..187b1dd --- /dev/null +++ b/pkg/client/fake/fake_association_test.go @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package fake + +import ( + "context" + "testing" + + "k8s.io/utils/ptr" + + apiv0042 "github.com/SlinkyProject/slurm-client/api/v0042" + apiv0043 "github.com/SlinkyProject/slurm-client/api/v0043" + apiv0044 "github.com/SlinkyProject/slurm-client/api/v0044" + apiv0045 "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/client" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestFakeClient_V0042AssocRoundTrip(t *testing.T) { + ctx := context.Background() + assoc := &types.V0042Assoc{V0042Assoc: apiv0042.V0042Assoc{ + Cluster: ptr.To("linux"), + Account: ptr.To("research"), + User: "alice", + }} + c := NewClientBuilder().WithObjects(assoc).Build() + + got := &types.V0042Assoc{} + if err := c.Get(ctx, assoc.GetKey(), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.User != "alice" { + t.Errorf("got assoc user %q, want alice", got.User) + } +} + +func TestFakeClient_V0043AssocRoundTrip(t *testing.T) { + ctx := context.Background() + assoc := &types.V0043Assoc{V0043Assoc: apiv0043.V0043Assoc{ + Cluster: ptr.To("linux"), + Account: ptr.To("research"), + User: "alice", + }} + c := NewClientBuilder().WithObjects(assoc).Build() + + got := &types.V0043Assoc{} + if err := c.Get(ctx, assoc.GetKey(), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.User != "alice" { + t.Errorf("got assoc user %q, want alice", got.User) + } +} + +func TestFakeClient_V0044AssocRoundTrip(t *testing.T) { + ctx := context.Background() + assoc := &types.V0044Assoc{V0044Assoc: apiv0044.V0044Assoc{ + Cluster: ptr.To("linux"), + Account: ptr.To("research"), + User: "alice", + }} + c := NewClientBuilder().WithObjects(assoc).Build() + + got := &types.V0044Assoc{} + if err := c.Get(ctx, assoc.GetKey(), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.User != "alice" { + t.Errorf("got assoc user %q, want alice", got.User) + } +} + +func TestFakeClient_V0045AssocRoundTrip(t *testing.T) { + ctx := context.Background() + assoc := &types.V0045Assoc{V0045Assoc: apiv0045.V0045Assoc{ + Cluster: ptr.To("linux"), + Account: ptr.To("research"), + User: "alice", + }} + c := NewClientBuilder().WithObjects(assoc).Build() + + got := &types.V0045Assoc{} + if err := c.Get(ctx, assoc.GetKey(), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.User != "alice" { + t.Errorf("got assoc user %q, want alice", got.User) + } +} diff --git a/pkg/client/fake/fake_user_test.go b/pkg/client/fake/fake_user_test.go new file mode 100644 index 0000000..2271758 --- /dev/null +++ b/pkg/client/fake/fake_user_test.go @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package fake + +import ( + "context" + "testing" + + apiv0042 "github.com/SlinkyProject/slurm-client/api/v0042" + apiv0043 "github.com/SlinkyProject/slurm-client/api/v0043" + apiv0044 "github.com/SlinkyProject/slurm-client/api/v0044" + apiv0045 "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/client" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/types" +) + +func TestFakeClient_V0042UserRoundTrip(t *testing.T) { + ctx := context.Background() + user := &types.V0042User{V0042User: apiv0042.V0042User{Name: "alice"}} + c := NewClientBuilder().WithObjects(user).Build() + + got := &types.V0042User{} + if err := c.Get(ctx, object.ObjectKey("alice"), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.Name != "alice" { + t.Errorf("got user %q, want alice", got.Name) + } + + list := &types.V0042UserList{} + if err := c.List(ctx, list); err != nil { + t.Fatalf("List() error = %v", err) + } + if len(list.Items) != 1 { + t.Errorf("List() len = %d, want 1", len(list.Items)) + } +} + +func TestFakeClient_V0043UserRoundTrip(t *testing.T) { + ctx := context.Background() + user := &types.V0043User{V0043User: apiv0043.V0043User{Name: "alice"}} + c := NewClientBuilder().WithObjects(user).Build() + + got := &types.V0043User{} + if err := c.Get(ctx, object.ObjectKey("alice"), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.Name != "alice" { + t.Errorf("got user %q, want alice", got.Name) + } + + list := &types.V0043UserList{} + if err := c.List(ctx, list); err != nil { + t.Fatalf("List() error = %v", err) + } + if len(list.Items) != 1 { + t.Errorf("List() len = %d, want 1", len(list.Items)) + } +} + +func TestFakeClient_V0044UserRoundTrip(t *testing.T) { + ctx := context.Background() + user := &types.V0044User{V0044User: apiv0044.V0044User{Name: "alice"}} + c := NewClientBuilder().WithObjects(user).Build() + + got := &types.V0044User{} + if err := c.Get(ctx, object.ObjectKey("alice"), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.Name != "alice" { + t.Errorf("got user %q, want alice", got.Name) + } + + list := &types.V0044UserList{} + if err := c.List(ctx, list); err != nil { + t.Fatalf("List() error = %v", err) + } + if len(list.Items) != 1 { + t.Errorf("List() len = %d, want 1", len(list.Items)) + } +} + +func TestFakeClient_V0045UserRoundTrip(t *testing.T) { + ctx := context.Background() + user := &types.V0045User{V0045User: apiv0045.V0045User{Name: "alice"}} + c := NewClientBuilder().WithObjects(user).Build() + + got := &types.V0045User{} + if err := c.Get(ctx, object.ObjectKey("alice"), got, &client.GetOptions{SkipCache: true}); err != nil { + t.Fatalf("Get() error = %v", err) + } + if got.Name != "alice" { + t.Errorf("got user %q, want alice", got.Name) + } + + list := &types.V0045UserList{} + if err := c.List(ctx, list); err != nil { + t.Fatalf("List() error = %v", err) + } + if len(list.Items) != 1 { + t.Errorf("List() len = %d, want 1", len(list.Items)) + } +} diff --git a/pkg/client/informer.go b/pkg/client/informer.go index 1561c0a..f4c6d72 100644 --- a/pkg/client/informer.go +++ b/pkg/client/informer.go @@ -159,6 +159,12 @@ func (i *informerCache) doListInformer() { list = &types.V0042PartitionInfoList{} case types.ObjectTypeV0042Reconfigure: panic("Reconfigure is not supported, this scenario should have been avoided.") + case types.ObjectTypeV0042Account: + list = &types.V0042AccountList{} + case types.ObjectTypeV0042User: + list = &types.V0042UserList{} + case types.ObjectTypeV0042Assoc: + list = &types.V0042AssocList{} case types.ObjectTypeV0042Stats: list = &types.V0042StatsList{} @@ -174,6 +180,12 @@ func (i *informerCache) doListInformer() { list = &types.V0043PartitionInfoList{} case types.ObjectTypeV0043Reconfigure: panic("Reconfigure is not supported, this scenario should have been avoided.") + case types.ObjectTypeV0043Account: + list = &types.V0043AccountList{} + case types.ObjectTypeV0043User: + list = &types.V0043UserList{} + case types.ObjectTypeV0043Assoc: + list = &types.V0043AssocList{} case types.ObjectTypeV0043Stats: list = &types.V0043StatsList{} @@ -191,6 +203,12 @@ func (i *informerCache) doListInformer() { panic("Reconfigure is not supported, this scenario should have been avoided.") case types.ObjectTypeV0044ReservationInfo: list = &types.V0044ReservationInfoList{} + case types.ObjectTypeV0044Account: + list = &types.V0044AccountList{} + case types.ObjectTypeV0044User: + list = &types.V0044UserList{} + case types.ObjectTypeV0044Assoc: + list = &types.V0044AssocList{} case types.ObjectTypeV0044NodeResourceLayout: panic("NodeResouceLayout is not supported, this scenario should have been avoided.") case types.ObjectTypeV0044Stats: @@ -210,6 +228,12 @@ func (i *informerCache) doListInformer() { panic("Reconfigure is not supported, this scenario should have been avoided.") case types.ObjectTypeV0045ReservationInfo: list = &types.V0045ReservationInfoList{} + case types.ObjectTypeV0045Account: + list = &types.V0045AccountList{} + case types.ObjectTypeV0045User: + list = &types.V0045UserList{} + case types.ObjectTypeV0045Assoc: + list = &types.V0045AssocList{} case types.ObjectTypeV0045NodeResourceLayout: panic("NodeResouceLayout is not supported, this scenario should have been avoided.") case types.ObjectTypeV0045Stats: @@ -292,6 +316,12 @@ func (i *informerCache) doGetInformer(key object.ObjectKey) { obj = &types.V0042PartitionInfo{} case types.ObjectTypeV0042Reconfigure: panic("Reconfigure is not supported, this scenario should have been avoided.") + case types.ObjectTypeV0042Account: + obj = &types.V0042Account{} + case types.ObjectTypeV0042User: + obj = &types.V0042User{} + case types.ObjectTypeV0042Assoc: + obj = &types.V0042Assoc{} case types.ObjectTypeV0042Stats: obj = &types.V0042Stats{} @@ -307,6 +337,12 @@ func (i *informerCache) doGetInformer(key object.ObjectKey) { obj = &types.V0043PartitionInfo{} case types.ObjectTypeV0043Reconfigure: panic("Reconfigure is not supported, this scenario should have been avoided.") + case types.ObjectTypeV0043Account: + obj = &types.V0043Account{} + case types.ObjectTypeV0043User: + obj = &types.V0043User{} + case types.ObjectTypeV0043Assoc: + obj = &types.V0043Assoc{} case types.ObjectTypeV0043Stats: obj = &types.V0043Stats{} @@ -324,6 +360,12 @@ func (i *informerCache) doGetInformer(key object.ObjectKey) { panic("Reconfigure is not supported, this scenario should have been avoided.") case types.ObjectTypeV0044ReservationInfo: obj = &types.V0044ReservationInfo{} + case types.ObjectTypeV0044Account: + obj = &types.V0044Account{} + case types.ObjectTypeV0044User: + obj = &types.V0044User{} + case types.ObjectTypeV0044Assoc: + obj = &types.V0044Assoc{} case types.ObjectTypeV0044Stats: obj = &types.V0044Stats{} @@ -341,6 +383,12 @@ func (i *informerCache) doGetInformer(key object.ObjectKey) { panic("Reconfigure is not supported, this scenario should have been avoided.") case types.ObjectTypeV0045ReservationInfo: obj = &types.V0045ReservationInfo{} + case types.ObjectTypeV0045Account: + obj = &types.V0045Account{} + case types.ObjectTypeV0045User: + obj = &types.V0045User{} + case types.ObjectTypeV0045Assoc: + obj = &types.V0045Assoc{} case types.ObjectTypeV0045Stats: obj = &types.V0045Stats{} @@ -568,6 +616,15 @@ func (i *informerCache) Get(ctx context.Context, key object.ObjectKey, obj objec case *types.V0042PartitionInfo: cache := entry.object.(*types.V0042PartitionInfo) *o = *cache + case *types.V0042Account: + cache := entry.object.(*types.V0042Account) + *o = *cache + case *types.V0042User: + cache := entry.object.(*types.V0042User) + *o = *cache + case *types.V0042Assoc: + cache := entry.object.(*types.V0042Assoc) + *o = *cache case *types.V0042Stats: cache := entry.object.(*types.V0042Stats) *o = *cache @@ -586,6 +643,15 @@ func (i *informerCache) Get(ctx context.Context, key object.ObjectKey, obj objec case *types.V0043PartitionInfo: cache := entry.object.(*types.V0043PartitionInfo) *o = *cache + case *types.V0043Account: + cache := entry.object.(*types.V0043Account) + *o = *cache + case *types.V0043User: + cache := entry.object.(*types.V0043User) + *o = *cache + case *types.V0043Assoc: + cache := entry.object.(*types.V0043Assoc) + *o = *cache case *types.V0043Stats: cache := entry.object.(*types.V0043Stats) *o = *cache @@ -607,6 +673,15 @@ func (i *informerCache) Get(ctx context.Context, key object.ObjectKey, obj objec case *types.V0044ReservationInfo: cache := entry.object.(*types.V0044ReservationInfo) *o = *cache + case *types.V0044Account: + cache := entry.object.(*types.V0044Account) + *o = *cache + case *types.V0044User: + cache := entry.object.(*types.V0044User) + *o = *cache + case *types.V0044Assoc: + cache := entry.object.(*types.V0044Assoc) + *o = *cache case *types.V0044Stats: cache := entry.object.(*types.V0044Stats) *o = *cache @@ -628,6 +703,15 @@ func (i *informerCache) Get(ctx context.Context, key object.ObjectKey, obj objec case *types.V0045ReservationInfo: cache := entry.object.(*types.V0045ReservationInfo) *o = *cache + case *types.V0045Account: + cache := entry.object.(*types.V0045Account) + *o = *cache + case *types.V0045User: + cache := entry.object.(*types.V0045User) + *o = *cache + case *types.V0045Assoc: + cache := entry.object.(*types.V0045Assoc) + *o = *cache case *types.V0045Stats: cache := entry.object.(*types.V0045Stats) *o = *cache diff --git a/pkg/types/V0042Account.go b/pkg/types/V0042Account.go new file mode 100644 index 0000000..52844b7 --- /dev/null +++ b/pkg/types/V0042Account.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0042Account = "V0042Account" +) + +type V0042Account struct { + api.V0042Account +} + +// GetKey implements Object. +func (o *V0042Account) GetKey() object.ObjectKey { + return object.ObjectKey(o.Name) +} + +// GetType implements Object. +func (o *V0042Account) GetType() object.ObjectType { + return ObjectTypeV0042Account +} + +// DeepCopyObject implements Object. +func (o *V0042Account) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0042Account) DeepCopy() *V0042Account { + out := new(V0042Account) + utils.RemarshalOrDie(o, out) + return out +} + +type V0042AccountList struct { + Items []V0042Account +} + +// GetType implements ObjectList. +func (o *V0042AccountList) GetType() object.ObjectType { + return ObjectTypeV0042Account +} + +// GetItems implements ObjectList. +func (o *V0042AccountList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0042AccountList) AppendItem(obj object.Object) { + out, ok := obj.(*V0042Account) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0042AccountList) DeepCopyObjectList() object.ObjectList { + out := new(V0042AccountList) + out.Items = make([]V0042Account, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0042Account_test.go b/pkg/types/V0042Account_test.go new file mode 100644 index 0000000..dd58530 --- /dev/null +++ b/pkg/types/V0042Account_test.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0042Account_GetKey(t *testing.T) { + acct := &V0042Account{V0042Account: api.V0042Account{Name: "research"}} + if got := acct.GetKey(); got != object.ObjectKey("research") { + t.Errorf("GetKey() = %q, want %q", got, "research") + } + if got := acct.GetType(); got != ObjectTypeV0042Account { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0042Account) + } +} + +func TestV0042AccountList_GetItems(t *testing.T) { + list := &V0042AccountList{Items: []V0042Account{ + {V0042Account: api.V0042Account{Name: "a"}}, + {V0042Account: api.V0042Account{Name: "b"}}, + }} + if len(list.GetItems()) != 2 { + t.Errorf("GetItems() len = %d, want 2", len(list.GetItems())) + } +} diff --git a/pkg/types/V0042Assoc.go b/pkg/types/V0042Assoc.go new file mode 100644 index 0000000..2c6aa5e --- /dev/null +++ b/pkg/types/V0042Assoc.go @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0042Assoc = "V0042Assoc" +) + +type V0042Assoc struct { + api.V0042Assoc +} + +// GetKey implements Object. Key is "cluster/account/user/partition". +func (o *V0042Assoc) GetKey() object.ObjectKey { + return object.ObjectKey(fmt.Sprintf("%s/%s/%s/%s", + ptr.Deref(o.Cluster, ""), + ptr.Deref(o.Account, ""), + o.User, + ptr.Deref(o.Partition, ""), + )) +} + +// GetType implements Object. +func (o *V0042Assoc) GetType() object.ObjectType { + return ObjectTypeV0042Assoc +} + +// DeepCopyObject implements Object. +func (o *V0042Assoc) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0042Assoc) DeepCopy() *V0042Assoc { + out := new(V0042Assoc) + utils.RemarshalOrDie(o, out) + return out +} + +type V0042AssocList struct { + Items []V0042Assoc +} + +// GetType implements ObjectList. +func (o *V0042AssocList) GetType() object.ObjectType { + return ObjectTypeV0042Assoc +} + +// GetItems implements ObjectList. +func (o *V0042AssocList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0042AssocList) AppendItem(obj object.Object) { + out, ok := obj.(*V0042Assoc) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0042AssocList) DeepCopyObjectList() object.ObjectList { + out := new(V0042AssocList) + out.Items = make([]V0042Assoc, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0042Assoc_test.go b/pkg/types/V0042Assoc_test.go new file mode 100644 index 0000000..39e4d2f --- /dev/null +++ b/pkg/types/V0042Assoc_test.go @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0042Assoc_GetKey(t *testing.T) { + assoc := &V0042Assoc{V0042Assoc: api.V0042Assoc{ + Cluster: ptr.To("linux"), + Account: ptr.To("research"), + User: "alice", + Partition: ptr.To("debug"), + }} + want := object.ObjectKey("linux/research/alice/debug") + if got := assoc.GetKey(); got != want { + t.Errorf("GetKey() = %q, want %q", got, want) + } + if got := assoc.GetType(); got != ObjectTypeV0042Assoc { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0042Assoc) + } +} + +func TestV0042AssocList_GetItems(t *testing.T) { + list := &V0042AssocList{Items: []V0042Assoc{ + {V0042Assoc: api.V0042Assoc{User: "alice"}}, + {V0042Assoc: api.V0042Assoc{User: "bob"}}, + }} + if len(list.GetItems()) != 2 { + t.Errorf("GetItems() len = %d, want 2", len(list.GetItems())) + } +} diff --git a/pkg/types/V0042User.go b/pkg/types/V0042User.go new file mode 100644 index 0000000..4780357 --- /dev/null +++ b/pkg/types/V0042User.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0042User = "V0042User" +) + +type V0042User struct { + api.V0042User +} + +// GetKey implements Object. +func (o *V0042User) GetKey() object.ObjectKey { + return object.ObjectKey(o.Name) +} + +// GetType implements Object. +func (o *V0042User) GetType() object.ObjectType { + return ObjectTypeV0042User +} + +// DeepCopyObject implements Object. +func (o *V0042User) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0042User) DeepCopy() *V0042User { + out := new(V0042User) + utils.RemarshalOrDie(o, out) + return out +} + +type V0042UserList struct { + Items []V0042User +} + +// GetType implements ObjectList. +func (o *V0042UserList) GetType() object.ObjectType { + return ObjectTypeV0042User +} + +// GetItems implements ObjectList. +func (o *V0042UserList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0042UserList) AppendItem(obj object.Object) { + out, ok := obj.(*V0042User) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0042UserList) DeepCopyObjectList() object.ObjectList { + out := new(V0042UserList) + out.Items = make([]V0042User, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0042User_test.go b/pkg/types/V0042User_test.go new file mode 100644 index 0000000..9b94cb9 --- /dev/null +++ b/pkg/types/V0042User_test.go @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0042" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0042User_GetKey(t *testing.T) { + user := &V0042User{V0042User: api.V0042User{Name: "alice"}} + if got := user.GetKey(); got != object.ObjectKey("alice") { + t.Errorf("GetKey() = %q, want %q", got, "alice") + } + if got := user.GetType(); got != ObjectTypeV0042User { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0042User) + } +} + +func TestV0042UserList_GetItems(t *testing.T) { + list := &V0042UserList{Items: []V0042User{ + {V0042User: api.V0042User{Name: "alice"}}, + }} + if len(list.GetItems()) != 1 { + t.Errorf("GetItems() len = %d, want 1", len(list.GetItems())) + } +} diff --git a/pkg/types/V0043Account.go b/pkg/types/V0043Account.go new file mode 100644 index 0000000..17b5336 --- /dev/null +++ b/pkg/types/V0043Account.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0043Account = "V0043Account" +) + +type V0043Account struct { + api.V0043Account +} + +// GetKey implements Object. +func (o *V0043Account) GetKey() object.ObjectKey { + return object.ObjectKey(o.Name) +} + +// GetType implements Object. +func (o *V0043Account) GetType() object.ObjectType { + return ObjectTypeV0043Account +} + +// DeepCopyObject implements Object. +func (o *V0043Account) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0043Account) DeepCopy() *V0043Account { + out := new(V0043Account) + utils.RemarshalOrDie(o, out) + return out +} + +type V0043AccountList struct { + Items []V0043Account +} + +// GetType implements ObjectList. +func (o *V0043AccountList) GetType() object.ObjectType { + return ObjectTypeV0043Account +} + +// GetItems implements ObjectList. +func (o *V0043AccountList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0043AccountList) AppendItem(obj object.Object) { + out, ok := obj.(*V0043Account) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0043AccountList) DeepCopyObjectList() object.ObjectList { + out := new(V0043AccountList) + out.Items = make([]V0043Account, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0043Account_test.go b/pkg/types/V0043Account_test.go new file mode 100644 index 0000000..64782f5 --- /dev/null +++ b/pkg/types/V0043Account_test.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0043Account_GetKey(t *testing.T) { + acct := &V0043Account{V0043Account: api.V0043Account{Name: "research"}} + if got := acct.GetKey(); got != object.ObjectKey("research") { + t.Errorf("GetKey() = %q, want %q", got, "research") + } + if got := acct.GetType(); got != ObjectTypeV0043Account { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0043Account) + } +} + +func TestV0043AccountList_GetItems(t *testing.T) { + list := &V0043AccountList{Items: []V0043Account{ + {V0043Account: api.V0043Account{Name: "a"}}, + {V0043Account: api.V0043Account{Name: "b"}}, + }} + if len(list.GetItems()) != 2 { + t.Errorf("GetItems() len = %d, want 2", len(list.GetItems())) + } +} diff --git a/pkg/types/V0043Assoc.go b/pkg/types/V0043Assoc.go new file mode 100644 index 0000000..e4f8c7a --- /dev/null +++ b/pkg/types/V0043Assoc.go @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0043Assoc = "V0043Assoc" +) + +type V0043Assoc struct { + api.V0043Assoc +} + +// GetKey implements Object. Key is "cluster/account/user/partition". +func (o *V0043Assoc) GetKey() object.ObjectKey { + return object.ObjectKey(fmt.Sprintf("%s/%s/%s/%s", + ptr.Deref(o.Cluster, ""), + ptr.Deref(o.Account, ""), + o.User, + ptr.Deref(o.Partition, ""), + )) +} + +// GetType implements Object. +func (o *V0043Assoc) GetType() object.ObjectType { + return ObjectTypeV0043Assoc +} + +// DeepCopyObject implements Object. +func (o *V0043Assoc) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0043Assoc) DeepCopy() *V0043Assoc { + out := new(V0043Assoc) + utils.RemarshalOrDie(o, out) + return out +} + +type V0043AssocList struct { + Items []V0043Assoc +} + +// GetType implements ObjectList. +func (o *V0043AssocList) GetType() object.ObjectType { + return ObjectTypeV0043Assoc +} + +// GetItems implements ObjectList. +func (o *V0043AssocList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0043AssocList) AppendItem(obj object.Object) { + out, ok := obj.(*V0043Assoc) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0043AssocList) DeepCopyObjectList() object.ObjectList { + out := new(V0043AssocList) + out.Items = make([]V0043Assoc, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0043Assoc_test.go b/pkg/types/V0043Assoc_test.go new file mode 100644 index 0000000..91aa26a --- /dev/null +++ b/pkg/types/V0043Assoc_test.go @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0043Assoc_GetKey(t *testing.T) { + assoc := &V0043Assoc{V0043Assoc: api.V0043Assoc{ + Cluster: ptr.To("linux"), + Account: ptr.To("research"), + User: "alice", + Partition: ptr.To("debug"), + }} + want := object.ObjectKey("linux/research/alice/debug") + if got := assoc.GetKey(); got != want { + t.Errorf("GetKey() = %q, want %q", got, want) + } + if got := assoc.GetType(); got != ObjectTypeV0043Assoc { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0043Assoc) + } +} + +func TestV0043AssocList_GetItems(t *testing.T) { + list := &V0043AssocList{Items: []V0043Assoc{ + {V0043Assoc: api.V0043Assoc{User: "alice"}}, + {V0043Assoc: api.V0043Assoc{User: "bob"}}, + }} + if len(list.GetItems()) != 2 { + t.Errorf("GetItems() len = %d, want 2", len(list.GetItems())) + } +} diff --git a/pkg/types/V0043User.go b/pkg/types/V0043User.go new file mode 100644 index 0000000..7de70f7 --- /dev/null +++ b/pkg/types/V0043User.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0043User = "V0043User" +) + +type V0043User struct { + api.V0043User +} + +// GetKey implements Object. +func (o *V0043User) GetKey() object.ObjectKey { + return object.ObjectKey(o.Name) +} + +// GetType implements Object. +func (o *V0043User) GetType() object.ObjectType { + return ObjectTypeV0043User +} + +// DeepCopyObject implements Object. +func (o *V0043User) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0043User) DeepCopy() *V0043User { + out := new(V0043User) + utils.RemarshalOrDie(o, out) + return out +} + +type V0043UserList struct { + Items []V0043User +} + +// GetType implements ObjectList. +func (o *V0043UserList) GetType() object.ObjectType { + return ObjectTypeV0043User +} + +// GetItems implements ObjectList. +func (o *V0043UserList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0043UserList) AppendItem(obj object.Object) { + out, ok := obj.(*V0043User) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0043UserList) DeepCopyObjectList() object.ObjectList { + out := new(V0043UserList) + out.Items = make([]V0043User, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0043User_test.go b/pkg/types/V0043User_test.go new file mode 100644 index 0000000..72c2dd2 --- /dev/null +++ b/pkg/types/V0043User_test.go @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0043" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0043User_GetKey(t *testing.T) { + user := &V0043User{V0043User: api.V0043User{Name: "alice"}} + if got := user.GetKey(); got != object.ObjectKey("alice") { + t.Errorf("GetKey() = %q, want %q", got, "alice") + } + if got := user.GetType(); got != ObjectTypeV0043User { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0043User) + } +} + +func TestV0043UserList_GetItems(t *testing.T) { + list := &V0043UserList{Items: []V0043User{ + {V0043User: api.V0043User{Name: "alice"}}, + }} + if len(list.GetItems()) != 1 { + t.Errorf("GetItems() len = %d, want 1", len(list.GetItems())) + } +} diff --git a/pkg/types/V0044Account.go b/pkg/types/V0044Account.go new file mode 100644 index 0000000..2140442 --- /dev/null +++ b/pkg/types/V0044Account.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0044Account = "V0044Account" +) + +type V0044Account struct { + api.V0044Account +} + +// GetKey implements Object. +func (o *V0044Account) GetKey() object.ObjectKey { + return object.ObjectKey(o.Name) +} + +// GetType implements Object. +func (o *V0044Account) GetType() object.ObjectType { + return ObjectTypeV0044Account +} + +// DeepCopyObject implements Object. +func (o *V0044Account) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0044Account) DeepCopy() *V0044Account { + out := new(V0044Account) + utils.RemarshalOrDie(o, out) + return out +} + +type V0044AccountList struct { + Items []V0044Account +} + +// GetType implements ObjectList. +func (o *V0044AccountList) GetType() object.ObjectType { + return ObjectTypeV0044Account +} + +// GetItems implements ObjectList. +func (o *V0044AccountList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0044AccountList) AppendItem(obj object.Object) { + out, ok := obj.(*V0044Account) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0044AccountList) DeepCopyObjectList() object.ObjectList { + out := new(V0044AccountList) + out.Items = make([]V0044Account, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0044Account_test.go b/pkg/types/V0044Account_test.go new file mode 100644 index 0000000..731aa03 --- /dev/null +++ b/pkg/types/V0044Account_test.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0044Account_GetKey(t *testing.T) { + acct := &V0044Account{V0044Account: api.V0044Account{Name: "research"}} + if got := acct.GetKey(); got != object.ObjectKey("research") { + t.Errorf("GetKey() = %q, want %q", got, "research") + } + if got := acct.GetType(); got != ObjectTypeV0044Account { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0044Account) + } +} + +func TestV0044AccountList_GetItems(t *testing.T) { + list := &V0044AccountList{Items: []V0044Account{ + {V0044Account: api.V0044Account{Name: "a"}}, + {V0044Account: api.V0044Account{Name: "b"}}, + }} + if len(list.GetItems()) != 2 { + t.Errorf("GetItems() len = %d, want 2", len(list.GetItems())) + } +} diff --git a/pkg/types/V0044Assoc.go b/pkg/types/V0044Assoc.go new file mode 100644 index 0000000..601fb98 --- /dev/null +++ b/pkg/types/V0044Assoc.go @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0044Assoc = "V0044Assoc" +) + +type V0044Assoc struct { + api.V0044Assoc +} + +// GetKey implements Object. Key is "cluster/account/user/partition". +func (o *V0044Assoc) GetKey() object.ObjectKey { + return object.ObjectKey(fmt.Sprintf("%s/%s/%s/%s", + ptr.Deref(o.Cluster, ""), + ptr.Deref(o.Account, ""), + o.User, + ptr.Deref(o.Partition, ""), + )) +} + +// GetType implements Object. +func (o *V0044Assoc) GetType() object.ObjectType { + return ObjectTypeV0044Assoc +} + +// DeepCopyObject implements Object. +func (o *V0044Assoc) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0044Assoc) DeepCopy() *V0044Assoc { + out := new(V0044Assoc) + utils.RemarshalOrDie(o, out) + return out +} + +type V0044AssocList struct { + Items []V0044Assoc +} + +// GetType implements ObjectList. +func (o *V0044AssocList) GetType() object.ObjectType { + return ObjectTypeV0044Assoc +} + +// GetItems implements ObjectList. +func (o *V0044AssocList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0044AssocList) AppendItem(obj object.Object) { + out, ok := obj.(*V0044Assoc) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0044AssocList) DeepCopyObjectList() object.ObjectList { + out := new(V0044AssocList) + out.Items = make([]V0044Assoc, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0044Assoc_test.go b/pkg/types/V0044Assoc_test.go new file mode 100644 index 0000000..76a36ec --- /dev/null +++ b/pkg/types/V0044Assoc_test.go @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0044Assoc_GetKey(t *testing.T) { + assoc := &V0044Assoc{V0044Assoc: api.V0044Assoc{ + Cluster: ptr.To("linux"), + Account: ptr.To("research"), + User: "alice", + Partition: ptr.To("debug"), + }} + want := object.ObjectKey("linux/research/alice/debug") + if got := assoc.GetKey(); got != want { + t.Errorf("GetKey() = %q, want %q", got, want) + } + if got := assoc.GetType(); got != ObjectTypeV0044Assoc { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0044Assoc) + } +} + +func TestV0044AssocList_GetItems(t *testing.T) { + list := &V0044AssocList{Items: []V0044Assoc{ + {V0044Assoc: api.V0044Assoc{User: "alice"}}, + {V0044Assoc: api.V0044Assoc{User: "bob"}}, + }} + if len(list.GetItems()) != 2 { + t.Errorf("GetItems() len = %d, want 2", len(list.GetItems())) + } +} diff --git a/pkg/types/V0044User.go b/pkg/types/V0044User.go new file mode 100644 index 0000000..2341ab2 --- /dev/null +++ b/pkg/types/V0044User.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0044User = "V0044User" +) + +type V0044User struct { + api.V0044User +} + +// GetKey implements Object. +func (o *V0044User) GetKey() object.ObjectKey { + return object.ObjectKey(o.Name) +} + +// GetType implements Object. +func (o *V0044User) GetType() object.ObjectType { + return ObjectTypeV0044User +} + +// DeepCopyObject implements Object. +func (o *V0044User) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0044User) DeepCopy() *V0044User { + out := new(V0044User) + utils.RemarshalOrDie(o, out) + return out +} + +type V0044UserList struct { + Items []V0044User +} + +// GetType implements ObjectList. +func (o *V0044UserList) GetType() object.ObjectType { + return ObjectTypeV0044User +} + +// GetItems implements ObjectList. +func (o *V0044UserList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0044UserList) AppendItem(obj object.Object) { + out, ok := obj.(*V0044User) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0044UserList) DeepCopyObjectList() object.ObjectList { + out := new(V0044UserList) + out.Items = make([]V0044User, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0044User_test.go b/pkg/types/V0044User_test.go new file mode 100644 index 0000000..681c4ee --- /dev/null +++ b/pkg/types/V0044User_test.go @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0044" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0044User_GetKey(t *testing.T) { + user := &V0044User{V0044User: api.V0044User{Name: "alice"}} + if got := user.GetKey(); got != object.ObjectKey("alice") { + t.Errorf("GetKey() = %q, want %q", got, "alice") + } + if got := user.GetType(); got != ObjectTypeV0044User { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0044User) + } +} + +func TestV0044UserList_GetItems(t *testing.T) { + list := &V0044UserList{Items: []V0044User{ + {V0044User: api.V0044User{Name: "alice"}}, + }} + if len(list.GetItems()) != 1 { + t.Errorf("GetItems() len = %d, want 1", len(list.GetItems())) + } +} diff --git a/pkg/types/V0045Account.go b/pkg/types/V0045Account.go new file mode 100644 index 0000000..01c1edf --- /dev/null +++ b/pkg/types/V0045Account.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0045Account = "V0045Account" +) + +type V0045Account struct { + api.V0045Account +} + +// GetKey implements Object. +func (o *V0045Account) GetKey() object.ObjectKey { + return object.ObjectKey(o.Name) +} + +// GetType implements Object. +func (o *V0045Account) GetType() object.ObjectType { + return ObjectTypeV0045Account +} + +// DeepCopyObject implements Object. +func (o *V0045Account) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0045Account) DeepCopy() *V0045Account { + out := new(V0045Account) + utils.RemarshalOrDie(o, out) + return out +} + +type V0045AccountList struct { + Items []V0045Account +} + +// GetType implements ObjectList. +func (o *V0045AccountList) GetType() object.ObjectType { + return ObjectTypeV0045Account +} + +// GetItems implements ObjectList. +func (o *V0045AccountList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0045AccountList) AppendItem(obj object.Object) { + out, ok := obj.(*V0045Account) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0045AccountList) DeepCopyObjectList() object.ObjectList { + out := new(V0045AccountList) + out.Items = make([]V0045Account, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0045Account_test.go b/pkg/types/V0045Account_test.go new file mode 100644 index 0000000..6a207ae --- /dev/null +++ b/pkg/types/V0045Account_test.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0045Account_GetKey(t *testing.T) { + acct := &V0045Account{V0045Account: api.V0045Account{Name: "research"}} + if got := acct.GetKey(); got != object.ObjectKey("research") { + t.Errorf("GetKey() = %q, want %q", got, "research") + } + if got := acct.GetType(); got != ObjectTypeV0045Account { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0045Account) + } +} + +func TestV0045AccountList_GetItems(t *testing.T) { + list := &V0045AccountList{Items: []V0045Account{ + {V0045Account: api.V0045Account{Name: "a"}}, + {V0045Account: api.V0045Account{Name: "b"}}, + }} + if len(list.GetItems()) != 2 { + t.Errorf("GetItems() len = %d, want 2", len(list.GetItems())) + } +} diff --git a/pkg/types/V0045Assoc.go b/pkg/types/V0045Assoc.go new file mode 100644 index 0000000..06f6048 --- /dev/null +++ b/pkg/types/V0045Assoc.go @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "fmt" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0045Assoc = "V0045Assoc" +) + +type V0045Assoc struct { + api.V0045Assoc +} + +// GetKey implements Object. Key is "cluster/account/user/partition". +func (o *V0045Assoc) GetKey() object.ObjectKey { + return object.ObjectKey(fmt.Sprintf("%s/%s/%s/%s", + ptr.Deref(o.Cluster, ""), + ptr.Deref(o.Account, ""), + o.User, + ptr.Deref(o.Partition, ""), + )) +} + +// GetType implements Object. +func (o *V0045Assoc) GetType() object.ObjectType { + return ObjectTypeV0045Assoc +} + +// DeepCopyObject implements Object. +func (o *V0045Assoc) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0045Assoc) DeepCopy() *V0045Assoc { + out := new(V0045Assoc) + utils.RemarshalOrDie(o, out) + return out +} + +type V0045AssocList struct { + Items []V0045Assoc +} + +// GetType implements ObjectList. +func (o *V0045AssocList) GetType() object.ObjectType { + return ObjectTypeV0045Assoc +} + +// GetItems implements ObjectList. +func (o *V0045AssocList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0045AssocList) AppendItem(obj object.Object) { + out, ok := obj.(*V0045Assoc) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0045AssocList) DeepCopyObjectList() object.ObjectList { + out := new(V0045AssocList) + out.Items = make([]V0045Assoc, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0045Assoc_test.go b/pkg/types/V0045Assoc_test.go new file mode 100644 index 0000000..7993352 --- /dev/null +++ b/pkg/types/V0045Assoc_test.go @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + "k8s.io/utils/ptr" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0045Assoc_GetKey(t *testing.T) { + assoc := &V0045Assoc{V0045Assoc: api.V0045Assoc{ + Cluster: ptr.To("linux"), + Account: ptr.To("research"), + User: "alice", + Partition: ptr.To("debug"), + }} + want := object.ObjectKey("linux/research/alice/debug") + if got := assoc.GetKey(); got != want { + t.Errorf("GetKey() = %q, want %q", got, want) + } + if got := assoc.GetType(); got != ObjectTypeV0045Assoc { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0045Assoc) + } +} + +func TestV0045AssocList_GetItems(t *testing.T) { + list := &V0045AssocList{Items: []V0045Assoc{ + {V0045Assoc: api.V0045Assoc{User: "alice"}}, + {V0045Assoc: api.V0045Assoc{User: "bob"}}, + }} + if len(list.GetItems()) != 2 { + t.Errorf("GetItems() len = %d, want 2", len(list.GetItems())) + } +} diff --git a/pkg/types/V0045User.go b/pkg/types/V0045User.go new file mode 100644 index 0000000..03b3aeb --- /dev/null +++ b/pkg/types/V0045User.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/object" + "github.com/SlinkyProject/slurm-client/pkg/utils" +) + +const ( + ObjectTypeV0045User = "V0045User" +) + +type V0045User struct { + api.V0045User +} + +// GetKey implements Object. +func (o *V0045User) GetKey() object.ObjectKey { + return object.ObjectKey(o.Name) +} + +// GetType implements Object. +func (o *V0045User) GetType() object.ObjectType { + return ObjectTypeV0045User +} + +// DeepCopyObject implements Object. +func (o *V0045User) DeepCopyObject() object.Object { + return o.DeepCopy() +} + +func (o *V0045User) DeepCopy() *V0045User { + out := new(V0045User) + utils.RemarshalOrDie(o, out) + return out +} + +type V0045UserList struct { + Items []V0045User +} + +// GetType implements ObjectList. +func (o *V0045UserList) GetType() object.ObjectType { + return ObjectTypeV0045User +} + +// GetItems implements ObjectList. +func (o *V0045UserList) GetItems() []object.Object { + list := make([]object.Object, len(o.Items)) + for i, item := range o.Items { + list[i] = item.DeepCopyObject() + } + return list +} + +// AppendItem implements ObjectList. +func (o *V0045UserList) AppendItem(obj object.Object) { + out, ok := obj.(*V0045User) + if ok { + utils.RemarshalOrDie(obj, out) + o.Items = append(o.Items, *out) + } +} + +// DeepCopyObjectList implements ObjectList. +func (o *V0045UserList) DeepCopyObjectList() object.ObjectList { + out := new(V0045UserList) + out.Items = make([]V0045User, len(o.Items)) + for i, item := range o.Items { + out.Items[i] = *item.DeepCopy() + } + return out +} diff --git a/pkg/types/V0045User_test.go b/pkg/types/V0045User_test.go new file mode 100644 index 0000000..2141ea8 --- /dev/null +++ b/pkg/types/V0045User_test.go @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC. +// SPDX-License-Identifier: Apache-2.0 + +package types + +import ( + "testing" + + api "github.com/SlinkyProject/slurm-client/api/v0045" + "github.com/SlinkyProject/slurm-client/pkg/object" +) + +func TestV0045User_GetKey(t *testing.T) { + user := &V0045User{V0045User: api.V0045User{Name: "alice"}} + if got := user.GetKey(); got != object.ObjectKey("alice") { + t.Errorf("GetKey() = %q, want %q", got, "alice") + } + if got := user.GetType(); got != ObjectTypeV0045User { + t.Errorf("GetType() = %q, want %q", got, ObjectTypeV0045User) + } +} + +func TestV0045UserList_GetItems(t *testing.T) { + list := &V0045UserList{Items: []V0045User{ + {V0045User: api.V0045User{Name: "alice"}}, + }} + if len(list.GetItems()) != 1 { + t.Errorf("GetItems() len = %d, want 1", len(list.GetItems())) + } +}