Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2ad7b3d
chore: point slurm-client at fork and drop v0041 reference
giuliocalzo Jun 23, 2026
0700e0d
feat: add shared accounting API types (limits, enums, association)
giuliocalzo Jun 23, 2026
b67c86d
feat: add Account API type
giuliocalzo Jun 23, 2026
9105543
feat: add User API type
giuliocalzo Jun 23, 2026
ee658e6
feat: add Account and User defaulting
giuliocalzo Jun 23, 2026
78fe93d
refactor: split accounting_shared_types and rename to AssociationLimits
giuliocalzo Jun 23, 2026
552c843
feat: add sacctmgr slurmcontrol with Account/User/limits mapping
giuliocalzo Jun 23, 2026
976cbe6
feat: add Account controller with finalizer and status
giuliocalzo Jun 23, 2026
dbf48ff
feat: add User controller with account dependency gate
giuliocalzo Jun 23, 2026
471d54e
feat: add validating webhooks for Account and User
giuliocalzo Jun 23, 2026
2af5d24
feat: wire Account/User controllers and webhooks, regen manifests
giuliocalzo Jun 23, 2026
088a482
docs: add Account and User sample manifests
giuliocalzo Jun 23, 2026
c8ff8e4
fix: address golangci-lint findings in slurmcontrol limits mapping
giuliocalzo Jun 23, 2026
867a9fe
feat(sacctmgr): emit Kubernetes events from Account/User controllers
giuliocalzo Jun 23, 2026
a5fda8d
fix(user-crd): bound CEL validation inputs to stay under cost budget
giuliocalzo Jun 23, 2026
0588caf
docs(usage): add guide for creating Accounts and Users
giuliocalzo Jun 23, 2026
5db4fb7
build: point slurm-client replace at fork branch pseudo-version
giuliocalzo Jun 23, 2026
41a6ee5
fix(sacctmgr): set cluster on slurmdbd associations
giuliocalzo Jun 23, 2026
3b63674
test(e2e): add Account and User CRD lifecycle test
giuliocalzo Jun 23, 2026
fa95b02
fix(sacctmgr): correct AssociationLimits to v0044 association paths
giuliocalzo Jun 23, 2026
dc3f182
fix(sacctmgr): default account description/org and tolerate 304
giuliocalzo Jun 23, 2026
11f25ed
feat(accounting): default Account/User optional fields
giuliocalzo Jun 23, 2026
17dd82c
feat(helm): manage Account and User CRs via the slurm chart
giuliocalzo Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,30 @@ resources:
webhooks:
validation: true
webhookVersion: v1beta1
- api:
crdVersion: v1beta1
namespaced: true
controller: true
domain: slurm.net
group: slinky
kind: Account
path: github.com/SlinkyProject/slurm-operator/api/v1beta1
version: v1beta1
webhooks:
validation: true
webhookVersion: v1beta1
- api:
crdVersion: v1beta1
namespaced: true
controller: true
domain: slurm.net
group: slinky
kind: User
path: github.com/SlinkyProject/slurm-operator/api/v1beta1
version: v1beta1
webhooks:
validation: true
webhookVersion: v1beta1
- api:
crdVersion: v1
namespaced: true
Expand Down
98 changes: 98 additions & 0 deletions api/v1beta1/account_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC.
// SPDX-License-Identifier: Apache-2.0

package v1beta1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
AccountKind = "Account"
)

var (
AccountGVK = GroupVersion.WithKind(AccountKind)
AccountAPIVersion = GroupVersion.String()
)

// DeletionPolicy controls what happens to the Slurm entity when the CR is deleted.
// +kubebuilder:validation:Enum=Delete;Orphan
type DeletionPolicy string

const (
DeletionPolicyDelete DeletionPolicy = "Delete"
DeletionPolicyOrphan DeletionPolicy = "Orphan"
)

// AccountSpec defines the desired state of Account.
type AccountSpec struct {
// controllerRef references the target Controller CR.
// +required
ControllerRef corev1.LocalObjectReference `json:"controllerRef"`

// accountName in Slurm; defaults to metadata.name if empty.
// +optional
AccountName string `json:"accountName,omitempty"`

// description of the account; defaults to accountName if empty.
// +optional
Description string `json:"description,omitempty"`

// organization to which the account belongs; defaults to "root" if empty.
// +optional
Organization string `json:"organization,omitempty"`

// parentAccount for hierarchy; defaults to "root".
// +optional
ParentAccount *string `json:"parentAccount,omitempty"`

// deletionPolicy controls the Slurm-side entity on CR deletion.
// +optional
// +default:="Delete"
DeletionPolicy DeletionPolicy `json:"deletionPolicy,omitempty"`

// limits applied to this account's association.
// +optional
Limits AssociationLimits `json:"limits,omitempty"`
}

// AccountStatus defines the observed state of Account.
type AccountStatus struct {
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=acct
// +kubebuilder:printcolumn:name="ACCOUNT",type="string",JSONPath=".spec.accountName",description="The Slurm account name."
// +kubebuilder:printcolumn:name="PARENT",type="string",JSONPath=".spec.parentAccount",description="The parent account."
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"

// Account is the Schema for the accounts API.
type Account struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec AccountSpec `json:"spec,omitempty"`
Status AccountStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// AccountList contains a list of Account.
type AccountList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Account `json:"items"`
}

func init() {
SchemeBuilder.Register(&Account{}, &AccountList{})
}
18 changes: 18 additions & 0 deletions api/v1beta1/account_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package v1beta1

import "testing"

func TestAccount_GVK(t *testing.T) {
if AccountKind != "Account" {
t.Fatalf("AccountKind = %q", AccountKind)
}
if AccountGVK.Kind != "Account" {
t.Fatalf("AccountGVK.Kind = %q", AccountGVK.Kind)
}
}

func TestDeletionPolicyConstants(t *testing.T) {
if DeletionPolicyDelete != "Delete" || DeletionPolicyOrphan != "Orphan" {
t.Fatalf("unexpected DeletionPolicy values")
}
}
63 changes: 63 additions & 0 deletions api/v1beta1/association_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC.
// SPDX-License-Identifier: Apache-2.0

package v1beta1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// AssociationLimits captures the common Slurm association limits/QoS/fairshare.
// Unset fields are not managed by the operator.
type AssociationLimits struct {
// Fairshare is an integer number of shares, or the literal "parent".
// +optional
Fairshare *string `json:"fairshare,omitempty"`

// Priority added to jobs using this association.
// +optional
Priority *int32 `json:"priority,omitempty"`

// QOS is the list of QOS names allowed for this association.
// +optional
QOS []string `json:"qos,omitempty"`

// DefaultQOS for this association; must be present in QOS.
// +optional
DefaultQOS *string `json:"defaultQos,omitempty"`

// GrpTRES is the aggregate TRES limit, e.g. {"cpu":"10","gres/gpu":"4"}.
// +optional
GrpTRES map[string]string `json:"grpTRES,omitempty"`

// +optional
GrpJobs *int32 `json:"grpJobs,omitempty"`
// +optional
GrpSubmitJobs *int32 `json:"grpSubmitJobs,omitempty"`
// +optional
GrpWall *metav1.Duration `json:"grpWall,omitempty"`

// +optional
MaxJobs *int32 `json:"maxJobs,omitempty"`
// +optional
MaxSubmitJobs *int32 `json:"maxSubmitJobs,omitempty"`
// MaxTRESPerJob is the per-job TRES limit, e.g. {"cpu":"4"}.
// +optional
MaxTRESPerJob map[string]string `json:"maxTRESPerJob,omitempty"`
// +optional
MaxWallPerJob *metav1.Duration `json:"maxWallPerJob,omitempty"`
}

// UserAssociation is a single account membership for a User.
type UserAssociation struct {
// account name this user belongs to.
// +required
// +kubebuilder:validation:MaxLength=64
Account string `json:"account"`

// partition scopes this association to a Slurm partition.
// +optional
Partition *string `json:"partition,omitempty"`

// limits for this specific membership.
// +optional
Limits AssociationLimits `json:"limits,omitempty"`
}
101 changes: 101 additions & 0 deletions api/v1beta1/user_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: Copyright (C) SchedMD LLC.
// SPDX-License-Identifier: Apache-2.0

package v1beta1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
UserKind = "User"
)

var (
UserGVK = GroupVersion.WithKind(UserKind)
UserAPIVersion = GroupVersion.String()
)

// AdminLevel is the Slurm administrator level granted to a user.
// +kubebuilder:validation:Enum=None;Operator;Administrator
type AdminLevel string

const (
AdminLevelNone AdminLevel = "None"
AdminLevelOperator AdminLevel = "Operator"
AdminLevelAdministrator AdminLevel = "Administrator"
)

// UserSpec defines the desired state of User.
// +kubebuilder:validation:XValidation:rule="self.defaultAccount == '' || self.associations.exists(a, a.account == self.defaultAccount)", message="defaultAccount must match one of the associations"
type UserSpec struct {
// controllerRef references the target Controller CR.
// +required
ControllerRef corev1.LocalObjectReference `json:"controllerRef"`

// userName in Slurm; defaults to metadata.name if empty.
// +optional
UserName string `json:"userName,omitempty"`

// adminLevel granted to the user.
// +optional
// +default:="None"
AdminLevel AdminLevel `json:"adminLevel,omitempty"`

// defaultAccount must match one of the associations' account.
// Defaults to the first association's account if empty.
// +optional
// +kubebuilder:validation:MaxLength=64
DefaultAccount string `json:"defaultAccount,omitempty"`

// associations are the account memberships for this user.
// +required
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=128
Associations []UserAssociation `json:"associations"`

// deletionPolicy controls the Slurm-side entity on CR deletion.
// +optional
// +default:="Delete"
DeletionPolicy DeletionPolicy `json:"deletionPolicy,omitempty"`
}

// UserStatus defines the observed state of User.
type UserStatus struct {
// +optional
// +patchMergeKey=type
// +patchStrategy=merge
// +listType=map
// +listMapKey=type
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=slurmuser
// +kubebuilder:printcolumn:name="USER",type="string",JSONPath=".spec.userName",description="The Slurm user name."
// +kubebuilder:printcolumn:name="DEFAULT ACCOUNT",type="string",JSONPath=".spec.defaultAccount",description="The user's default account."
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"

// User is the Schema for the users API.
type User struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec UserSpec `json:"spec,omitempty"`
Status UserStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// UserList contains a list of User.
type UserList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []User `json:"items"`
}

func init() {
SchemeBuilder.Register(&User{}, &UserList{})
}
18 changes: 18 additions & 0 deletions api/v1beta1/user_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package v1beta1

import "testing"

func TestUser_GVK(t *testing.T) {
if UserKind != "User" {
t.Fatalf("UserKind = %q", UserKind)
}
if UserGVK.Kind != "User" {
t.Fatalf("UserGVK.Kind = %q", UserGVK.Kind)
}
}

func TestAdminLevelConstants(t *testing.T) {
if AdminLevelNone != "None" || AdminLevelOperator != "Operator" || AdminLevelAdministrator != "Administrator" {
t.Fatalf("unexpected AdminLevel values")
}
}
Loading