Skip to content

Commit 6f3e60b

Browse files
committed
optimize webhook by parsing managedJobsNamespaceSelector at startup
1 parent 607dfef commit 6f3e60b

File tree

1 file changed

+57
-36
lines changed

1 file changed

+57
-36
lines changed

internal/webhook/appwrapper_webhook.go

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
authv1 "k8s.io/api/authorization/v1"
2525
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2626
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27+
"k8s.io/apimachinery/pkg/labels"
2728
"k8s.io/apimachinery/pkg/runtime"
2829
"k8s.io/apimachinery/pkg/runtime/schema"
2930
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -54,36 +55,42 @@ const (
5455
QueueNameLabel = "kueue.x-k8s.io/queue-name"
5556
)
5657

57-
type AppWrapperWebhook struct {
58-
Config *config.AppWrapperConfig
59-
SubjectAccessReviewer authClientv1.SubjectAccessReviewInterface
60-
client client.Client
61-
DiscoveryClient *discovery.DiscoveryClient
58+
type rbacACSupport struct {
59+
discoveryClient *discovery.DiscoveryClient
60+
subjectAccessReviewer authClientv1.SubjectAccessReviewInterface
6261
kindToResourceCache map[string]string
6362
}
6463

64+
type appWrapperWebhook struct {
65+
client client.Client
66+
defaultQueueName string
67+
enableKueueIntegrations bool
68+
manageJobsWithoutQueueName bool
69+
managedJobsNamespaceSelector labels.Selector
70+
userRBACAdmissionCheck bool
71+
72+
// support for userRBACAdmissionCheck; will be nil if it is not enabled
73+
rbacACSupport *rbacACSupport
74+
}
75+
6576
//+kubebuilder:webhook:path=/mutate-workload-codeflare-dev-v1beta2-appwrapper,mutating=true,failurePolicy=fail,sideEffects=None,groups=workload.codeflare.dev,resources=appwrappers,verbs=create,versions=v1beta2,name=mappwrapper.kb.io,admissionReviewVersions=v1
6677

67-
var _ webhook.CustomDefaulter = &AppWrapperWebhook{}
78+
var _ webhook.CustomDefaulter = &appWrapperWebhook{}
6879

6980
// Default fills in default values when an AppWrapper is created:
7081
// 1. Inject default queue name
7182
// 2. Ensure Suspend is set appropriately
7283
// 3. Add labels with the user name and id
73-
func (w *AppWrapperWebhook) Default(ctx context.Context, obj runtime.Object) error {
84+
func (w *appWrapperWebhook) Default(ctx context.Context, obj runtime.Object) error {
7485
aw := obj.(*workloadv1beta2.AppWrapper)
7586
log.FromContext(ctx).V(2).Info("Applying defaults", "job", aw)
7687

7788
// Queue name and Suspend
78-
if w.Config.EnableKueueIntegrations {
79-
if w.Config.DefaultQueueName != "" {
80-
aw.Labels = utilmaps.MergeKeepFirst(aw.Labels, map[string]string{QueueNameLabel: w.Config.DefaultQueueName})
89+
if w.enableKueueIntegrations {
90+
if w.defaultQueueName != "" {
91+
aw.Labels = utilmaps.MergeKeepFirst(aw.Labels, map[string]string{QueueNameLabel: w.defaultQueueName})
8192
}
82-
nsSelector, err := metav1.LabelSelectorAsSelector(w.Config.KueueJobReconciller.ManageJobsNamespaceSelector)
83-
if err != nil {
84-
return err
85-
}
86-
err = jobframework.ApplyDefaultForSuspend(ctx, (*wlc.AppWrapper)(aw), w.client, w.Config.KueueJobReconciller.ManageJobsWithoutQueueName, nsSelector)
93+
err := jobframework.ApplyDefaultForSuspend(ctx, (*wlc.AppWrapper)(aw), w.client, w.manageJobsWithoutQueueName, w.managedJobsNamespaceSelector)
8794
if err != nil {
8895
return err
8996
}
@@ -111,33 +118,33 @@ func (w *AppWrapperWebhook) Default(ctx context.Context, obj runtime.Object) err
111118

112119
//+kubebuilder:webhook:path=/validate-workload-codeflare-dev-v1beta2-appwrapper,mutating=false,failurePolicy=fail,sideEffects=None,groups=workload.codeflare.dev,resources=appwrappers,verbs=create;update,versions=v1beta2,name=vappwrapper.kb.io,admissionReviewVersions=v1
113120

114-
var _ webhook.CustomValidator = &AppWrapperWebhook{}
121+
var _ webhook.CustomValidator = &appWrapperWebhook{}
115122

116123
// ValidateCreate validates invariants when an AppWrapper is created
117-
func (w *AppWrapperWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
124+
func (w *appWrapperWebhook) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
118125
aw := obj.(*workloadv1beta2.AppWrapper)
119126
log.FromContext(ctx).V(2).Info("Validating create", "job", aw)
120127
allErrors := w.validateAppWrapperCreate(ctx, aw)
121-
if w.Config.EnableKueueIntegrations {
128+
if w.enableKueueIntegrations {
122129
allErrors = append(allErrors, jobframework.ValidateJobOnCreate((*wlc.AppWrapper)(aw))...)
123130
}
124131
return nil, allErrors.ToAggregate()
125132
}
126133

127134
// ValidateUpdate validates invariants when an AppWrapper is updated
128-
func (w *AppWrapperWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
135+
func (w *appWrapperWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
129136
oldAW := oldObj.(*workloadv1beta2.AppWrapper)
130137
newAW := newObj.(*workloadv1beta2.AppWrapper)
131138
log.FromContext(ctx).V(2).Info("Validating update", "job", newAW)
132139
allErrors := w.validateAppWrapperUpdate(oldAW, newAW)
133-
if w.Config.EnableKueueIntegrations {
140+
if w.enableKueueIntegrations {
134141
allErrors = append(allErrors, jobframework.ValidateJobOnUpdate((*wlc.AppWrapper)(oldAW), (*wlc.AppWrapper)(newAW))...)
135142
}
136143
return nil, allErrors.ToAggregate()
137144
}
138145

139146
// ValidateDelete is a noop for us, but is required to implement the CustomValidator interface
140-
func (w *AppWrapperWebhook) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) {
147+
func (w *appWrapperWebhook) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) {
141148
return nil, nil
142149
}
143150

@@ -151,7 +158,7 @@ func (w *AppWrapperWebhook) ValidateDelete(context.Context, runtime.Object) (adm
151158
// 3. AppWrappers must not contain any resources that the user could not create directly
152159
// 4. Every PodSet must be well-formed: the Path must exist and must be parseable as a PodSpecTemplate
153160
// 5. AppWrappers must contain between 1 and 8 PodSets (Kueue invariant)
154-
func (w *AppWrapperWebhook) validateAppWrapperCreate(ctx context.Context, aw *workloadv1beta2.AppWrapper) field.ErrorList {
161+
func (w *appWrapperWebhook) validateAppWrapperCreate(ctx context.Context, aw *workloadv1beta2.AppWrapper) field.ErrorList {
155162
allErrors := field.ErrorList{}
156163
components := aw.Spec.Components
157164
componentsPath := field.NewPath("spec").Child("components")
@@ -182,7 +189,7 @@ func (w *AppWrapperWebhook) validateAppWrapperCreate(ctx context.Context, aw *wo
182189
}
183190

184191
// 3. RBAC check: Perform SubjectAccessReview to verify user is entitled to create component
185-
if w.Config.UserRBACAdmissionCheck {
192+
if w.userRBACAdmissionCheck {
186193
ra := authv1.ResourceAttributes{
187194
Namespace: aw.Namespace,
188195
Verb: "create",
@@ -203,7 +210,7 @@ func (w *AppWrapperWebhook) validateAppWrapperCreate(ctx context.Context, aw *wo
203210
sar.Spec.Extra[k] = authv1.ExtraValue(v)
204211
}
205212
}
206-
sar, err = w.SubjectAccessReviewer.Create(ctx, sar, metav1.CreateOptions{})
213+
sar, err = w.rbacACSupport.subjectAccessReviewer.Create(ctx, sar, metav1.CreateOptions{})
207214
if err != nil {
208215
allErrors = append(allErrors, field.InternalError(compPath.Child("template"), err))
209216
} else {
@@ -253,7 +260,7 @@ func (w *AppWrapperWebhook) validateAppWrapperCreate(ctx context.Context, aw *wo
253260
}
254261

255262
// validateAppWrapperUpdate enforces deep immutablity of all fields that were validated by validateAppWrapperCreate
256-
func (w *AppWrapperWebhook) validateAppWrapperUpdate(old *workloadv1beta2.AppWrapper, new *workloadv1beta2.AppWrapper) field.ErrorList {
263+
func (w *appWrapperWebhook) validateAppWrapperUpdate(old *workloadv1beta2.AppWrapper, new *workloadv1beta2.AppWrapper) field.ErrorList {
257264
allErrors := field.ErrorList{}
258265
msg := "attempt to change immutable field"
259266
componentsPath := field.NewPath("spec").Child("components")
@@ -297,34 +304,48 @@ func (w *AppWrapperWebhook) validateAppWrapperUpdate(old *workloadv1beta2.AppWra
297304
return allErrors
298305
}
299306

300-
func (w *AppWrapperWebhook) lookupResource(gvk *schema.GroupVersionKind) string {
301-
if known, ok := w.kindToResourceCache[gvk.String()]; ok {
307+
func (w *appWrapperWebhook) lookupResource(gvk *schema.GroupVersionKind) string {
308+
if known, ok := w.rbacACSupport.kindToResourceCache[gvk.String()]; ok {
302309
return known
303310
}
304-
resources, err := w.DiscoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
311+
resources, err := w.rbacACSupport.discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
305312
if err != nil {
306313
return "*"
307314
}
308315
for _, r := range resources.APIResources {
309316
if r.Kind == gvk.Kind {
310-
w.kindToResourceCache[gvk.String()] = r.Name
317+
w.rbacACSupport.kindToResourceCache[gvk.String()] = r.Name
311318
return r.Name
312319
}
313320
}
314321
return "*"
315322
}
316323

317324
func SetupAppWrapperWebhook(mgr ctrl.Manager, awConfig *config.AppWrapperConfig) error {
318-
kubeClient, err := kubernetes.NewForConfig(mgr.GetConfig())
325+
nsSelector, err := metav1.LabelSelectorAsSelector(awConfig.KueueJobReconciller.ManageJobsNamespaceSelector)
319326
if err != nil {
320327
return err
321328
}
322-
wh := &AppWrapperWebhook{
323-
Config: awConfig,
324-
client: mgr.GetClient(),
325-
DiscoveryClient: kubeClient.DiscoveryClient,
326-
SubjectAccessReviewer: kubeClient.AuthorizationV1().SubjectAccessReviews(),
327-
kindToResourceCache: make(map[string]string),
329+
wh := &appWrapperWebhook{
330+
client: mgr.GetClient(),
331+
defaultQueueName: awConfig.DefaultQueueName,
332+
enableKueueIntegrations: awConfig.EnableKueueIntegrations,
333+
manageJobsWithoutQueueName: awConfig.KueueJobReconciller.ManageJobsWithoutQueueName,
334+
managedJobsNamespaceSelector: nsSelector,
335+
userRBACAdmissionCheck: awConfig.UserRBACAdmissionCheck,
336+
}
337+
338+
if awConfig.UserRBACAdmissionCheck {
339+
kubeClient, err := kubernetes.NewForConfig(mgr.GetConfig())
340+
if err != nil {
341+
return err
342+
}
343+
wh.rbacACSupport = &rbacACSupport{
344+
discoveryClient: kubeClient.DiscoveryClient,
345+
subjectAccessReviewer: kubeClient.AuthorizationV1().SubjectAccessReviews(),
346+
kindToResourceCache: make(map[string]string),
347+
}
348+
328349
}
329350

330351
return ctrl.NewWebhookManagedBy(mgr).

0 commit comments

Comments
 (0)