Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add labels for workload parent #1032

Merged
merged 4 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions pkg/controller/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ const (
// ignores this Job from admission, and takes control of its suspension
// status based on the admission status of the parent workload.
ParentWorkloadAnnotation = "kueue.x-k8s.io/parent-workload"

// ParentUIDLabel is the label key in the workload resource, that holds the UID of
// a parent.
ParentUIDLabel = "kueue.x-k8s.io/parent-uid"
achernevskii marked this conversation as resolved.
Show resolved Hide resolved
)
6 changes: 6 additions & 0 deletions pkg/controller/jobframework/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"context"
"errors"
"fmt"
"unicode/utf8"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -462,13 +463,18 @@ func (r *JobReconciler) constructWorkload(ctx context.Context, job GenericJob, o
ObjectMeta: metav1.ObjectMeta{
Name: GetWorkloadNameForOwnerWithGVK(object.GetName(), job.GetGVK()),
Namespace: object.GetNamespace(),
Labels: map[string]string{},
},
Spec: kueue.WorkloadSpec{
PodSets: resetMinCounts(podSets),
QueueName: QueueName(job),
},
}

if uid := string(job.Object().GetUID()); utf8.RuneCountInString(uid) < 64 && uid != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k8s validation just uses len
https://github.com/kubernetes/kubernetes/blob/2c6c4566eff972d6c1320b5f8ad795f88c822d09/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go#L169

I think using the rune len would be weaker.

I'm wondering if we should just try to put the label and let the apiserver return an error. Note that there is a regex check, so that could potentially fail as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should just try to put the label and let the apiserver return an error.

We can do that, but I would rather create a workload without a job-uid label, since this functionality is not essential for the controller. We could also log a warning if a label can't be added to a workload. Please let me know which of those three options is the best in your opinion.

Note that there is a regex check, so that could potentially fail as well.

According to this page, UIDs are standardized as ISO/IEC 9834-8 and as ITU-T X.667. And ISO/IEC 9834-8 says the following:

UUIDs forming a component of an OID are represented in ASN.1 value notation as the decimal representation of their integer value, but for all other display purposes it is more usual to represent them with hexadecimal digits with a hyphen separating the different fields within the 16-octet UUID.

Regex that kuberentes is using for label validation is the following (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?, if I'm not mistaken. This regex should match the UID format, so I think we're good here.

Copy link
Contributor Author

@achernevskii achernevskii Aug 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k8s validation just uses len

Replaced RuneCountInString call with len call in 866a09a

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do that, but I would rather create a workload without a job-uid label, since this functionality is not essential for the controller.

In that case, use IsValidLabelValue from https://github.com/kubernetes/kubernetes/blob/2c6c4566eff972d6c1320b5f8ad795f88c822d09/staging/src/k8s.io/apimachinery/pkg/util/validation/validation.go#L167

We could also log a warning if a label can't be added to a workload.

Logging is not visible to users. Webhooks support warnings https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#best-practices-and-warnings:~:text=Admission%20webhooks%20can%20optionally%20return%20warning%20messages%20that%20are%20returned%20to%20the%20requesting%20client%20in%20HTTP%20Warning%20headers%20with%20a%20warning%20code%20of%20299.%20Warnings%20can%20be%20sent%20with%20allowed%20or%20rejected%20admission%20responses.

But in our case, it's not end users creating Workloads, but users. Maybe a log is not so bad so administrators can take action.

UIDs are standardized as ISO/IEC 9834-8 and as ITU-T X.667.

I think there is a way where an end user can override the UID and potentially use something that wouldn't pass label validation. However this should be very uncommon.
The log should be enough :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced len label check with IsValidLabelValue, and added the log message in 0350ad8.

wl.Labels[controllerconsts.ParentUIDLabel] = uid
}

priorityClassName, p, err := r.extractPriority(ctx, podSets, job)
if err != nil {
return nil, err
Expand Down
33 changes: 32 additions & 1 deletion pkg/controller/jobs/job/job_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package job

import (
"strings"
"testing"
"time"

Expand All @@ -31,6 +32,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"

kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
controllerconsts "sigs.k8s.io/kueue/pkg/controller/constants"
"sigs.k8s.io/kueue/pkg/controller/jobframework"
"sigs.k8s.io/kueue/pkg/features"
"sigs.k8s.io/kueue/pkg/util/pointer"
Expand Down Expand Up @@ -307,7 +309,10 @@ var (
cmpopts.SortSlices(func(a, b kueue.Workload) bool {
return a.Name < b.Name
}),
cmpopts.IgnoreFields(kueue.Workload{}, "TypeMeta", "ObjectMeta"),
cmpopts.IgnoreFields(
kueue.Workload{}, "TypeMeta", "ObjectMeta.OwnerReferences",
"ObjectMeta.Name", "ObjectMeta.ResourceVersion",
),
cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime"),
}
)
Expand Down Expand Up @@ -424,16 +429,42 @@ func TestReconciler(t *testing.T) {
Clone().
Suspend(false).
Queue("test-queue").
UID("test-uid").
Obj(),
wantJob: *baseJobWrapper.
Clone().
Queue("test-queue").
UID("test-uid").
Obj(),
wantWorkloads: []kueue.Workload{
*utiltesting.MakeWorkload("job", "ns").
PodSets(*utiltesting.MakePodSet(kueue.DefaultPodSetName, 10).Request(corev1.ResourceCPU, "1").Obj()).
Queue("test-queue").
Priority(0).
SetLabels(map[string]string{
controllerconsts.ParentUIDLabel: "test-uid",
}).
Obj(),
},
},
"the workload without uid label is created when job's uid is longer than 63 characters": {
job: *baseJobWrapper.
Clone().
Suspend(false).
Queue("test-queue").
UID(strings.Repeat("long-uid", 8)).
Obj(),
wantJob: *baseJobWrapper.
Clone().
Queue("test-queue").
UID(strings.Repeat("long-uid", 8)).
Obj(),
wantWorkloads: []kueue.Workload{
*utiltesting.MakeWorkload("job", "ns").
PodSets(*utiltesting.MakePodSet(kueue.DefaultPodSetName, 10).Request(corev1.ResourceCPU, "1").Obj()).
Queue("test-queue").
Priority(0).
SetLabels(map[string]string{}).
Obj(),
},
},
Expand Down
5 changes: 5 additions & 0 deletions pkg/util/testing/wrappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ func (w *WorkloadWrapper) ReclaimablePods(rps ...kueue.ReclaimablePod) *Workload
return w
}

func (w *WorkloadWrapper) SetLabels(l map[string]string) *WorkloadWrapper {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (w *WorkloadWrapper) SetLabels(l map[string]string) *WorkloadWrapper {
func (w *WorkloadWrapper) Labels(l map[string]string) *WorkloadWrapper {

nit, but we generally don't use for functions like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed this method in 0350ad8.

w.Labels = l
return w
}

type PodSetWrapper struct{ kueue.PodSet }

func MakePodSet(name string, count int) *PodSetWrapper {
Expand Down