Skip to content

Commit 0d4315e

Browse files
Wei WengWei Weng
authored andcommitted
label namespace with parent-CRP
Signed-off-by: Wei Weng <[email protected]>
1 parent ad61196 commit 0d4315e

File tree

9 files changed

+297
-11
lines changed

9 files changed

+297
-11
lines changed

pkg/controllers/workapplier/apply.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ func (r *Reconciler) applyInDryRunMode(
5252
ctx context.Context,
5353
gvr *schema.GroupVersionResource,
5454
manifestObj, inMemberClusterObj *unstructured.Unstructured,
55+
parentCRPName string,
5556
) (*unstructured.Unstructured, error) {
57+
// Add parent-CRP label to namespace objects for namespace affinity support
58+
manifestObjCopy := manifestObj.DeepCopy()
59+
labelNamespaceWithParentCRP(manifestObjCopy, parentCRPName)
60+
5661
// In this method, Fleet will always use forced server-side apply
5762
// w/o optimistic lock for diff calculation.
5863
//
@@ -61,7 +66,7 @@ func (r *Reconciler) applyInDryRunMode(
6166
// before the comparison.
6267
//
6368
// Note that full comparison can be carried out directly without involving the apply op.
64-
return r.serverSideApply(ctx, gvr, manifestObj, inMemberClusterObj, true, false, true)
69+
return r.serverSideApply(ctx, gvr, manifestObjCopy, inMemberClusterObj, true, false, true)
6570
}
6671

6772
func (r *Reconciler) apply(
@@ -70,6 +75,7 @@ func (r *Reconciler) apply(
7075
manifestObj, inMemberClusterObj *unstructured.Unstructured,
7176
applyStrategy *fleetv1beta1.ApplyStrategy,
7277
expectedAppliedWorkOwnerRef *metav1.OwnerReference,
78+
parentCRPName string,
7379
) (*unstructured.Unstructured, error) {
7480
// Create a sanitized copy of the manifest object.
7581
//
@@ -84,6 +90,9 @@ func (r *Reconciler) apply(
8490
// backwards compatibility concerns.
8591
manifestObjCopy := sanitizeManifestObject(manifestObj)
8692

93+
// Add parent-CRP label to namespace objects for namespace affinity support
94+
labelNamespaceWithParentCRP(manifestObjCopy, parentCRPName)
95+
8796
// Compute the hash of the manifest object.
8897
//
8998
// Originally the manifest hash is kept only if three-way merge patch (client side apply)
@@ -129,7 +138,7 @@ func (r *Reconciler) apply(
129138

130139
// Create the object if it does not exist in the member cluster.
131140
if inMemberClusterObj == nil {
132-
return r.createManifestObject(ctx, gvr, manifestObjCopy)
141+
return r.createManifestObject(ctx, gvr, manifestObjCopy, parentCRPName)
133142
}
134143

135144
// Note: originally Fleet will add its owner reference and
@@ -199,6 +208,7 @@ func (r *Reconciler) createManifestObject(
199208
ctx context.Context,
200209
gvr *schema.GroupVersionResource,
201210
manifestObject *unstructured.Unstructured,
211+
parentCRPName string,
202212
) (*unstructured.Unstructured, error) {
203213
createOpts := metav1.CreateOptions{
204214
FieldManager: workFieldManagerName,
@@ -649,3 +659,36 @@ func shouldUseForcedServerSideApply(inMemberClusterObj *unstructured.Unstructure
649659
"GVK", inMemberClusterObj.GroupVersionKind(), "inMemberClusterObj", klog.KObj(inMemberClusterObj))
650660
return true
651661
}
662+
663+
// labelNamespaceWithParentCRP adds parent-CRP label to namespace objects being applied.
664+
// This supports the namespace affinity feature where ResourcePlacements can be scheduled
665+
// only on clusters that have the required namespace.
666+
func labelNamespaceWithParentCRP(manifestObj *unstructured.Unstructured, parentCRPName string) {
667+
// Only label if this is a namespace object
668+
if manifestObj.GetKind() != "Namespace" || manifestObj.GetAPIVersion() != "v1" {
669+
return
670+
}
671+
672+
// Skip if no parent CRP name is provided
673+
if parentCRPName == "" {
674+
klog.Warningf("No parent CRP name provided for namespace %s, skipping labeling",
675+
manifestObj.GetName())
676+
return
677+
}
678+
679+
// Get existing labels or create new map
680+
labels := manifestObj.GetLabels()
681+
if labels == nil {
682+
labels = make(map[string]string)
683+
}
684+
685+
// Add the parent-CRP label
686+
// Format: kubernetes-fleet.io/parent-CRP: {crp-name}
687+
labels[fleetv1beta1.PlacementTrackingLabel] = parentCRPName
688+
689+
// Set the updated labels back
690+
manifestObj.SetLabels(labels)
691+
692+
klog.V(2).InfoS("Added parent-CRP label to namespace",
693+
"namespace", manifestObj.GetName(), "crpName", parentCRPName)
694+
}

pkg/controllers/workapplier/apply_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,3 +552,106 @@ func TestShouldUseForcedServerSideApply(t *testing.T) {
552552
})
553553
}
554554
}
555+
556+
// TestLabelNamespaceWithParentCRP tests the labelNamespaceWithParentCRP function.
557+
func TestLabelNamespaceWithParentCRP(t *testing.T) {
558+
testCases := []struct {
559+
name string
560+
manifestObj client.Object
561+
parentCRPName string
562+
wantLabels map[string]string
563+
}{
564+
{
565+
name: "namespace object with no existing labels",
566+
manifestObj: &corev1.Namespace{
567+
TypeMeta: metav1.TypeMeta{
568+
APIVersion: "v1",
569+
Kind: "Namespace",
570+
},
571+
ObjectMeta: metav1.ObjectMeta{
572+
Name: "test-namespace",
573+
},
574+
},
575+
parentCRPName: "test-crp",
576+
wantLabels: map[string]string{
577+
fleetv1beta1.PlacementTrackingLabel: "test-crp",
578+
},
579+
},
580+
{
581+
name: "namespace object with existing labels",
582+
manifestObj: &corev1.Namespace{
583+
TypeMeta: metav1.TypeMeta{
584+
APIVersion: "v1",
585+
Kind: "Namespace",
586+
},
587+
ObjectMeta: metav1.ObjectMeta{
588+
Name: "test-namespace",
589+
Labels: map[string]string{
590+
"existing-label": "existing-value",
591+
},
592+
},
593+
},
594+
parentCRPName: "my-app-crp",
595+
wantLabels: map[string]string{
596+
"existing-label": "existing-value",
597+
fleetv1beta1.PlacementTrackingLabel: "my-app-crp",
598+
},
599+
},
600+
{
601+
name: "non-namespace object should not be modified",
602+
manifestObj: &appsv1.Deployment{
603+
TypeMeta: metav1.TypeMeta{
604+
APIVersion: "apps/v1",
605+
Kind: "Deployment",
606+
},
607+
ObjectMeta: metav1.ObjectMeta{
608+
Name: "test-deployment",
609+
Labels: map[string]string{
610+
"app": "test",
611+
},
612+
},
613+
},
614+
parentCRPName: "test-crp",
615+
wantLabels: map[string]string{
616+
"app": "test",
617+
},
618+
},
619+
{
620+
name: "empty parentCRPName should not add label",
621+
manifestObj: &corev1.Namespace{
622+
TypeMeta: metav1.TypeMeta{
623+
APIVersion: "v1",
624+
Kind: "Namespace",
625+
},
626+
ObjectMeta: metav1.ObjectMeta{
627+
Name: "test-namespace",
628+
Labels: map[string]string{
629+
"existing": "label",
630+
},
631+
},
632+
},
633+
parentCRPName: "",
634+
wantLabels: map[string]string{
635+
"existing": "label",
636+
},
637+
},
638+
}
639+
640+
for _, tc := range testCases {
641+
t.Run(tc.name, func(t *testing.T) {
642+
// Convert to unstructured and make a copy to avoid modifying the original test data
643+
manifestCopy := toUnstructured(t, tc.manifestObj).DeepCopy()
644+
645+
// Call the function under test
646+
labelNamespaceWithParentCRP(manifestCopy, tc.parentCRPName)
647+
648+
// Get the resulting labels
649+
gotLabels := manifestCopy.GetLabels()
650+
651+
// Compare the labels
652+
if diff := cmp.Diff(tc.wantLabels, gotLabels); diff != "" {
653+
t.Errorf("labelNamespaceWithParentCRP() labels mismatch (-want +got):\n%s", diff)
654+
}
655+
})
656+
}
657+
}

pkg/controllers/workapplier/drift_detection_takeover.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ func (r *Reconciler) takeOverPreExistingObject(
4747
manifestObj, inMemberClusterObj *unstructured.Unstructured,
4848
applyStrategy *fleetv1beta1.ApplyStrategy,
4949
expectedAppliedWorkOwnerRef *metav1.OwnerReference,
50+
parentCRPName string,
5051
) (*unstructured.Unstructured, []fleetv1beta1.PatchDetail, bool, error) {
5152
inMemberClusterObjCopy := inMemberClusterObj.DeepCopy()
5253
existingOwnerRefs := inMemberClusterObjCopy.GetOwnerReferences()
@@ -91,7 +92,7 @@ func (r *Reconciler) takeOverPreExistingObject(
9192
//
9293
// Note that the default takeover action is AlwaysApply.
9394
if applyStrategy.WhenToTakeOver == fleetv1beta1.WhenToTakeOverTypeIfNoDiff {
94-
configDiffs, diffCalculatedInDegradedMode, err := r.diffBetweenManifestAndInMemberClusterObjects(ctx, gvr, manifestObj, inMemberClusterObjCopy, applyStrategy.ComparisonOption)
95+
configDiffs, diffCalculatedInDegradedMode, err := r.diffBetweenManifestAndInMemberClusterObjects(ctx, gvr, manifestObj, inMemberClusterObjCopy, applyStrategy.ComparisonOption, parentCRPName)
9596
switch {
9697
case err != nil:
9798
return nil, nil, false, fmt.Errorf("failed to calculate configuration diffs between the manifest object and the object from the member cluster: %w", err)
@@ -121,10 +122,11 @@ func (r *Reconciler) diffBetweenManifestAndInMemberClusterObjects(
121122
gvr *schema.GroupVersionResource,
122123
manifestObj, inMemberClusterObj *unstructured.Unstructured,
123124
cmpOption fleetv1beta1.ComparisonOptionType,
125+
parentCRPName string,
124126
) ([]fleetv1beta1.PatchDetail, bool, error) {
125127
switch cmpOption {
126128
case fleetv1beta1.ComparisonOptionTypePartialComparison:
127-
return r.partialDiffBetweenManifestAndInMemberClusterObjects(ctx, gvr, manifestObj, inMemberClusterObj)
129+
return r.partialDiffBetweenManifestAndInMemberClusterObjects(ctx, gvr, manifestObj, inMemberClusterObj, parentCRPName)
128130
case fleetv1beta1.ComparisonOptionTypeFullComparison:
129131
// For the full comparison, Fleet compares directly the JSON representations of the
130132
// manifest object and the object in the member cluster.
@@ -143,10 +145,11 @@ func (r *Reconciler) partialDiffBetweenManifestAndInMemberClusterObjects(
143145
ctx context.Context,
144146
gvr *schema.GroupVersionResource,
145147
manifestObj, inMemberClusterObj *unstructured.Unstructured,
148+
parentCRPName string,
146149
) ([]fleetv1beta1.PatchDetail, bool, error) {
147150
// Fleet calculates the partial diff between two objects by running apply ops in the dry-run
148151
// mode.
149-
appliedObj, err := r.applyInDryRunMode(ctx, gvr, manifestObj, inMemberClusterObj)
152+
appliedObj, err := r.applyInDryRunMode(ctx, gvr, manifestObj, inMemberClusterObj, parentCRPName)
150153

151154
// After the dry-run apply op, all the managed fields should have been overwritten using the
152155
// values from the manifest object, while leaving all the unmanaged fields untouched. This

pkg/controllers/workapplier/drift_detection_takeover_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ func TestTakeOverPreExistingObject(t *testing.T) {
215215
tc.gvr,
216216
tc.manifestObj, tc.inMemberClusterObj,
217217
tc.applyStrategy,
218-
tc.expectedAppliedWorkOwnerRef)
218+
tc.expectedAppliedWorkOwnerRef,
219+
"test-crp")
219220
if tc.wantErred {
220221
if err == nil {
221222
t.Errorf("takeOverPreExistingObject() = nil, want erred")

pkg/controllers/workapplier/process.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@ func (r *Reconciler) processOneManifest(
165165
}
166166

167167
// Perform the apply op.
168-
appliedObj, err := r.apply(ctx, bundle.gvr, bundle.manifestObj, bundle.inMemberClusterObj, work.Spec.ApplyStrategy, expectedAppliedWorkOwnerRef)
168+
// Extract parent-CRP name from Work object labels for namespace affinity support
169+
parentCRPName := getParentCRPNameFromWork(work)
170+
171+
appliedObj, err := r.apply(ctx, bundle.gvr, bundle.manifestObj, bundle.inMemberClusterObj, work.Spec.ApplyStrategy, expectedAppliedWorkOwnerRef, parentCRPName)
169172
if err != nil {
170173
bundle.applyOrReportDiffErr = fmt.Errorf("failed to apply the manifest: %w", err)
171174
bundle.applyOrReportDiffResTyp = ApplyOrReportDiffResTypeFailedToApply
@@ -256,9 +259,12 @@ func (r *Reconciler) takeOverInMemberClusterObjectIfApplicable(
256259

257260
// Take over the object. Note that this steps adds only the owner reference; no other
258261
// fields are modified (on the object from the member cluster).
262+
// Extract parent-CRP name for namespace affinity support
263+
parentCRPName := getParentCRPNameFromWork(work)
264+
259265
takenOverInMemberClusterObj, configDiffs, diffCalculatedInDegradedMode, err := r.takeOverPreExistingObject(ctx,
260266
bundle.gvr, bundle.manifestObj, bundle.inMemberClusterObj,
261-
work.Spec.ApplyStrategy, expectedAppliedWorkOwnerRef)
267+
work.Spec.ApplyStrategy, expectedAppliedWorkOwnerRef, parentCRPName)
262268
switch {
263269
case err != nil:
264270
// An unexpected error has occurred.
@@ -371,10 +377,14 @@ func (r *Reconciler) reportDiffOnlyIfApplicable(
371377

372378
// The object has been created in the member cluster; Fleet will calculate the configuration
373379
// diffs between the manifest object and the object from the member cluster.
380+
// Extract parent-CRP name for namespace affinity support
381+
parentCRPName := getParentCRPNameFromWork(work)
382+
374383
configDiffs, diffCalculatedInDegradedMode, err := r.diffBetweenManifestAndInMemberClusterObjects(ctx,
375384
bundle.gvr,
376385
bundle.manifestObj, bundle.inMemberClusterObj,
377-
work.Spec.ApplyStrategy.ComparisonOption)
386+
work.Spec.ApplyStrategy.ComparisonOption,
387+
parentCRPName)
378388
switch {
379389
case err != nil:
380390
// Failed to calculate the configuration diffs.
@@ -463,10 +473,14 @@ func (r *Reconciler) performPreApplyDriftDetectionIfApplicable(
463473
return false
464474
default:
465475
// Run the drift detection process.
476+
// Extract parent-CRP name for namespace affinity support
477+
parentCRPName := getParentCRPNameFromWork(work)
478+
466479
drifts, driftsCalculatedInDegradedMode, err := r.diffBetweenManifestAndInMemberClusterObjects(ctx,
467480
bundle.gvr,
468481
bundle.manifestObj, bundle.inMemberClusterObj,
469-
work.Spec.ApplyStrategy.ComparisonOption)
482+
work.Spec.ApplyStrategy.ComparisonOption,
483+
parentCRPName)
470484
switch {
471485
case err != nil:
472486
// An unexpected error has occurred.
@@ -546,10 +560,14 @@ func (r *Reconciler) performPostApplyDriftDetectionIfApplicable(
546560
return false
547561
}
548562

563+
// Extract parent-CRP name for namespace affinity support
564+
parentCRPName := getParentCRPNameFromWork(work)
565+
549566
drifts, driftsCalculatedInDegradedMode, err := r.diffBetweenManifestAndInMemberClusterObjects(ctx,
550567
bundle.gvr,
551568
bundle.manifestObj, bundle.inMemberClusterObj,
552-
work.Spec.ApplyStrategy.ComparisonOption)
569+
work.Spec.ApplyStrategy.ComparisonOption,
570+
parentCRPName)
553571
switch {
554572
case err != nil:
555573
// An unexpected error has occurred.
@@ -595,3 +613,12 @@ func shouldPerformPostApplyDriftDetection(applyStrategy *fleetv1beta1.ApplyStrat
595613
// * The apply strategy dictates that drift detection should run in full comparison mode.
596614
return applyStrategy.ComparisonOption == fleetv1beta1.ComparisonOptionTypeFullComparison
597615
}
616+
617+
// getParentCRPNameFromWork extracts the parent CRP name from Work object labels.
618+
// Returns empty string if the label is not found.
619+
func getParentCRPNameFromWork(work *fleetv1beta1.Work) string {
620+
if workLabels := work.GetLabels(); workLabels != nil {
621+
return workLabels[fleetv1beta1.PlacementTrackingLabel]
622+
}
623+
return ""
624+
}

0 commit comments

Comments
 (0)