Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions apis/kueue/v1beta1/clusterqueue_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,24 @@ type ClusterQueueSpec struct {
// admissionScope indicates whether ClusterQueue uses the Admission Fair Sharing
// +optional
AdmissionScope *AdmissionScope `json:"admissionScope,omitempty"`

// excludeResourcePrefixes defines which resources should be ignored by
// Kueue for quota management in this ClusterQueue. Resources matching any
// of the prefixes will be excluded from quota calculations.
// When specified, this list is combined (union) with the global
// excludeResourcePrefixes from the Kueue Configuration.
// The prefix matching follows the same semantics as the global configuration:
// - An exact match of the resource name
// - A prefix match followed by a slash (e.g., "example.com" matches "example.com/gpu")
//
// Example: ["ephemeral-storage", "hugepages-", "example.com"]
//
// +listType=set
// +kubebuilder:validation:MaxItems=64
// +kubebuilder:validation:items:MaxLength=256
// +kubebuilder:validation:items:MinLength=1
// +optional
ExcludeResourcePrefixes []string `json:"excludeResourcePrefixes,omitempty"`
}

// AdmissionChecksStrategy defines a strategy for a AdmissionCheck.
Expand Down
2 changes: 2 additions & 0 deletions apis/kueue/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions apis/kueue/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions apis/kueue/v1beta2/clusterqueue_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,24 @@ type ClusterQueueSpec struct {
// admissionScope indicates whether ClusterQueue uses the Admission Fair Sharing
// +optional
AdmissionScope *AdmissionScope `json:"admissionScope,omitempty"`

// excludeResourcePrefixes defines which resources should be ignored by
// Kueue for quota management in this ClusterQueue. Resources matching any
// of the prefixes will be excluded from quota calculations.
// When specified, this list is combined (union) with the global
// excludeResourcePrefixes from the Kueue Configuration.
// The prefix matching follows the same semantics as the global configuration:
// - An exact match of the resource name
// - A prefix match followed by a slash (e.g., "example.com" matches "example.com/gpu")
//
// Example: ["ephemeral-storage", "hugepages-", "example.com"]
//
// +listType=set
// +kubebuilder:validation:MaxItems=64
// +kubebuilder:validation:items:MaxLength=256
// +kubebuilder:validation:items:MinLength=1
// +optional
ExcludeResourcePrefixes []string `json:"excludeResourcePrefixes,omitempty"`
}

// AdmissionChecksStrategy defines a strategy for a AdmissionCheck.
Expand Down
5 changes: 5 additions & 0 deletions apis/kueue/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions client-go/applyconfiguration/kueue/v1beta1/clusterqueuespec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions client-go/applyconfiguration/kueue/v1beta2/clusterqueuespec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions config/components/crd/bases/kueue.x-k8s.io_clusterqueues.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,25 @@ spec:
maxLength: 253
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
excludeResourcePrefixes:
description: |-
excludeResourcePrefixes defines which resources should be ignored by
Kueue for quota management in this ClusterQueue. Resources matching any
of the prefixes will be excluded from quota calculations.
When specified, this list is combined (union) with the global
excludeResourcePrefixes from the Kueue Configuration.
The prefix matching follows the same semantics as the global configuration:
- An exact match of the resource name
- A prefix match followed by a slash (e.g., "example.com" matches "example.com/gpu")

Example: ["ephemeral-storage", "hugepages-", "example.com"]
items:
maxLength: 256
minLength: 1
type: string
maxItems: 64
type: array
x-kubernetes-list-type: set
fairSharing:
description: |-
fairSharing defines the properties of the ClusterQueue when
Expand Down Expand Up @@ -894,6 +913,25 @@ spec:
maxLength: 253
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
excludeResourcePrefixes:
description: |-
excludeResourcePrefixes defines which resources should be ignored by
Kueue for quota management in this ClusterQueue. Resources matching any
of the prefixes will be excluded from quota calculations.
When specified, this list is combined (union) with the global
excludeResourcePrefixes from the Kueue Configuration.
The prefix matching follows the same semantics as the global configuration:
- An exact match of the resource name
- A prefix match followed by a slash (e.g., "example.com" matches "example.com/gpu")

Example: ["ephemeral-storage", "hugepages-", "example.com"]
items:
maxLength: 256
minLength: 1
type: string
maxItems: 64
type: array
x-kubernetes-list-type: set
fairSharing:
description: |-
fairSharing defines the properties of the ClusterQueue when
Expand Down
13 changes: 13 additions & 0 deletions pkg/cache/queue/cluster_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,24 @@ type ClusterQueue struct {
localQueuesInClusterQueue map[utilqueue.LocalQueueReference]bool

sw *stickyWorkload

// excludeResourcePrefixes contains the resource prefixes to exclude from quota management
// at the ClusterQueue level. These are merged with global exclusions.
excludeResourcePrefixes []string
}

func (c *ClusterQueue) GetName() kueue.ClusterQueueReference {
return c.name
}

// GetExcludeResourcePrefixes returns the ClusterQueue-specific resource exclusion prefixes.
// These should be merged with global exclusions when creating workload Info.
func (c *ClusterQueue) GetExcludeResourcePrefixes() []string {
c.rwm.RLock()
defer c.rwm.RUnlock()
return slices.Clone(c.excludeResourcePrefixes)
}

func workloadKey(i *workload.Info) workload.Reference {
return workload.Key(i.Obj)
}
Expand Down Expand Up @@ -162,6 +174,7 @@ func (c *ClusterQueue) Update(apiCQ *kueue.ClusterQueue) error {
}
c.namespaceSelector = nsSelector
c.active = apimeta.IsStatusConditionTrue(apiCQ.Status.Conditions, kueue.ClusterQueueActive)
c.excludeResourcePrefixes = slices.Clone(apiCQ.Spec.ExcludeResourcePrefixes)
return nil
}

Expand Down
108 changes: 108 additions & 0 deletions pkg/cache/queue/cluster_queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1138,3 +1138,111 @@ func TestFsAdmission(t *testing.T) {
})
}
}

func TestClusterQueueGetExcludeResourcePrefixes(t *testing.T) {
ctx, _ := utiltesting.ContextWithLog(t)

cases := map[string]struct {
cqExclusions []string
want []string
}{
"no exclusions": {
cqExclusions: []string{},
want: []string{},
},
"single exclusion": {
cqExclusions: []string{"example.com/"},
want: []string{"example.com/"},
},
"multiple exclusions": {
cqExclusions: []string{"example.com/", "foo.io/", "bar.io/"},
want: []string{"example.com/", "foo.io/", "bar.io/"},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
cq := newClusterQueueImpl(ctx, nil, defaultOrdering, testingclock.NewFakeClock(time.Now()), nil, false, nil)
apiCQ := utiltestingapi.MakeClusterQueue("test-cq").
ExcludeResourcePrefixes(tc.cqExclusions).
Obj()

err := cq.Update(apiCQ)
if err != nil {
t.Fatalf("Failed to update ClusterQueue: %v", err)
}

got := cq.GetExcludeResourcePrefixes()
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("Unexpected exclude resource prefixes (-want,+got):\n%s", diff)
}

// Verify that the returned slice is a copy (not the original)
if len(got) > 0 {
got[0] = "modified/"
unmodified := cq.GetExcludeResourcePrefixes()
if diff := cmp.Diff(tc.want, unmodified); diff != "" {
t.Errorf("Modifying returned slice affected internal state (-want,+got):\n%s", diff)
}
}
})
}
}

func TestClusterQueueUpdateExclusions(t *testing.T) {
ctx, _ := utiltesting.ContextWithLog(t)

cases := map[string]struct {
initialExclusions []string
updatedExclusions []string
}{
"empty to non-empty": {
initialExclusions: []string{},
updatedExclusions: []string{"example.com/"},
},
"non-empty to empty": {
initialExclusions: []string{"example.com/"},
updatedExclusions: []string{},
},
"update existing": {
initialExclusions: []string{"example.com/"},
updatedExclusions: []string{"foo.io/", "bar.io/"},
},
"no change": {
initialExclusions: []string{"example.com/"},
updatedExclusions: []string{"example.com/"},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
cq := newClusterQueueImpl(ctx, nil, defaultOrdering, testingclock.NewFakeClock(time.Now()), nil, false, nil)

// Set initial exclusions
apiCQ := utiltestingapi.MakeClusterQueue("test-cq").
ExcludeResourcePrefixes(tc.initialExclusions).
Obj()
err := cq.Update(apiCQ)
if err != nil {
t.Fatalf("Failed initial update: %v", err)
}

got := cq.GetExcludeResourcePrefixes()
if diff := cmp.Diff(tc.initialExclusions, got); diff != "" {
t.Errorf("Initial exclusions incorrect (-want,+got):\n%s", diff)
}

// Update exclusions
apiCQ.Spec.ExcludeResourcePrefixes = tc.updatedExclusions
err = cq.Update(apiCQ)
if err != nil {
t.Fatalf("Failed to update ClusterQueue: %v", err)
}

got = cq.GetExcludeResourcePrefixes()
if diff := cmp.Diff(tc.updatedExclusions, got); diff != "" {
t.Errorf("Updated exclusions incorrect (-want,+got):\n%s", diff)
}
})
}
}
Loading