Skip to content

A batch under an unlabelled owner can write the workloads that never followed the label #14484

Description

@thc1006

What happened:

A batch handed to UpdateWorkloadPriority for an owner that names no WorkloadPriorityClass can write the workloads that were never following one.

classifyWorkloadsForPriorityUpdate (pkg/controller/jobframework/reconciler.go) sorts the batch by comparing each workload's class name against the owner's. With no label on the owner, that name is "", and a workload with no priorityClassRef reads as naming the same empty class, so it lands in sameClassName. A sibling in the same batch that does hold a WorkloadPriorityClass reference lands in needsClassChange, which is what makes the helper resolve at all. applyResolvedPriority then writes both groups, so the unreferenced workloads are given whatever the fallback resolved to.

Their value did not come from the label. It came from a Pod PriorityClass or the cluster default, and this reconciliation is not supposed to touch it — #13781 and #8480 exclude those workloads on purpose.

What you expected to happen:

A workload with no priorityClassRef under an owner that names no class is left alone, whatever a sibling in the same batch is doing.

How to reproduce it (as minimally and precisely as possible):

The batch has to hold both shapes at once, so it needs a caller that passes several workloads:

  1. A Job with no kueue.x-k8s.io/priority-class label.
  2. One Workload for it with no priorityClassRef, carrying a value from a Pod PriorityClass.
  3. A second Workload in the same batch holding a WorkloadPriorityClass reference.
  4. Call UpdateWorkloadPriority with both.

The second one transitions, and the first is written along with it.

The path that reaches this today is reconciler.go's workload-slice branch, which since #13780 hands the helper the replacement and the quota-reserved slice it waits behind. Whether a real slice pair can hold those two shapes at once is the first thing to establish; I have not shown that it can.

Anything else we need to know?:

I had a guard for this in #13904 and took it back out. That PR is about resolving a WorkloadPriorityClass once per reconcile for a LeaderWorkerSet, and after narrowing it to the label path the guard is no longer load-bearing there: the no-label path hands the helper one workload at a time, and a single workload with no reference produces an empty needsClassChange, so nothing is written either way. Removing it left every LeaderWorkerSet test passing.

That left it as a change to shared behaviour for other callers, sitting in a PR about something else, so it belongs here instead. The guard was:

if jobPriorityClassName == "" && workload.HasNoPriority(wl) {
	continue
}

placed beside the existing quota-reserved skip in classifyWorkloadsForPriorityUpdate, with a case in TestUpdateWorkloadPriority named "leaves the unreferenced workloads of an unlabelled owner alone while a sibling transitions". Whether that is the right fix is worth deciding here rather than inheriting it.

Environment:

  • Kubernetes version (use kubectl version): N/A, read from the source
  • Kueue version (use git describe --tags --dirty --always): v0.20.0-devel
  • Cloud provider or hardware configuration: N/A
  • OS (e.g: cat /etc/os-release): N/A
  • Kernel (e.g. uname -a): N/A
  • Install tools: N/A
  • Others: N/A

This issue was written in part with the assistance of generative AI.
I verified the classifier and the callers against the tree myself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/integrationsWorkload integrationskind/bugCategorizes issue or PR as related to a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions