Skip to content

Commit 8bd12e0

Browse files
authored
feat: handle namespace resource selector change CRPS (#240)
1 parent 3049111 commit 8bd12e0

File tree

9 files changed

+1018
-364
lines changed

9 files changed

+1018
-364
lines changed

apis/placement/v1beta1/clusterresourceplacement_types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,17 @@ const (
12871287
// clusters, or an error has occurred.
12881288
// * Unknown: Fleet has not finished processing the diff reporting yet.
12891289
ClusterResourcePlacementDiffReportedConditionType ClusterResourcePlacementConditionType = "ClusterResourcePlacementDiffReported"
1290+
1291+
// ClusterResourcePlacementStatusSyncedConditionType indicates whether Fleet has successfully
1292+
// created or updated the ClusterResourcePlacementStatus object in the target namespace when
1293+
// StatusReportingScope is NamespaceAccessible.
1294+
//
1295+
// It can have the following condition statuses:
1296+
// * True: Fleet has successfully created or updated the ClusterResourcePlacementStatus object
1297+
// in the target namespace.
1298+
// * False: Fleet has failed to create or update the ClusterResourcePlacementStatus object
1299+
// in the target namespace.
1300+
ClusterResourcePlacementStatusSyncedConditionType ClusterResourcePlacementConditionType = "ClusterResourcePlacementStatusSynced"
12901301
)
12911302

12921303
// ResourcePlacementConditionType defines a specific condition of a resource placement object.

pkg/controllers/placement/controller.go

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,19 @@ func (r *Reconciler) handleUpdate(ctx context.Context, placementObj fleetv1beta1
142142
}
143143
}
144144

145+
// Validate namespace selector consistency for NamespaceAccessible CRPs.
146+
if isNamespaceAccessibleCRP(placementObj) {
147+
isValid, err := r.validateNamespaceSelectorConsistency(ctx, placementObj)
148+
if err != nil {
149+
klog.V(2).ErrorS(err, "Namespace resource selector validation failed for NamespaceAccessible CRP", "placement", placementKObj)
150+
return ctrl.Result{}, err
151+
}
152+
if !isValid {
153+
klog.V(2).InfoS("Invalid Namespace resource selector specified for NamespaceAccessible CRP, stopping reconciliation", "placement", placementKObj)
154+
return ctrl.Result{}, nil
155+
}
156+
}
157+
145158
// validate the resource selectors first before creating any snapshot
146159
envelopeObjCount, selectedResources, selectedResourceIDs, err := r.selectResourcesForPlacement(placementObj)
147160
if err != nil {
@@ -160,16 +173,17 @@ func (r *Reconciler) handleUpdate(ctx context.Context, placementObj fleetv1beta1
160173
}
161174
placementObj.SetConditions(scheduleCondition)
162175

163-
// Sync ClusterResourcePlacementStatus object if StatusReportingScope is NamespaceAccessible
164-
if syncErr := r.syncClusterResourcePlacementStatus(ctx, placementObj); syncErr != nil {
165-
klog.ErrorS(syncErr, "Failed to sync ClusterResourcePlacementStatus", "placement", placementKObj)
166-
return ctrl.Result{}, syncErr
167-
}
168-
169176
if updateErr := r.Client.Status().Update(ctx, placementObj); updateErr != nil {
170177
klog.ErrorS(updateErr, "Failed to update the status", "placement", placementKObj)
171178
return ctrl.Result{}, controller.NewUpdateIgnoreConflictError(updateErr)
172179
}
180+
klog.V(2).InfoS("Updated the placement status with scheduled condition", "placement", placementKObj)
181+
182+
if isNamespaceAccessibleCRP(placementObj) {
183+
if err := r.handleNamespaceAccessibleCRP(ctx, placementObj); err != nil {
184+
return ctrl.Result{}, err
185+
}
186+
}
173187

174188
// no need to retry faster, the user needs to fix the resource selectors
175189
return ctrl.Result{RequeueAfter: controllerResyncPeriod}, nil
@@ -212,18 +226,18 @@ func (r *Reconciler) handleUpdate(ctx context.Context, placementObj fleetv1beta1
212226
return ctrl.Result{}, err
213227
}
214228

215-
// Sync ClusterResourcePlacementStatus object if StatusReportingScope is NamespaceAccessible
216-
if err := r.syncClusterResourcePlacementStatus(ctx, placementObj); err != nil {
217-
klog.ErrorS(err, "Failed to sync ClusterResourcePlacementStatus", "placement", placementKObj)
218-
return ctrl.Result{}, err
219-
}
220-
221229
if err := r.Client.Status().Update(ctx, placementObj); err != nil {
222230
klog.ErrorS(err, "Failed to update the status", "placement", placementKObj)
223231
return ctrl.Result{}, err
224232
}
225233
klog.V(2).InfoS("Updated the placement status", "placement", placementKObj)
226234

235+
if isNamespaceAccessibleCRP(placementObj) {
236+
if err := r.handleNamespaceAccessibleCRP(ctx, placementObj); err != nil {
237+
return ctrl.Result{}, err
238+
}
239+
}
240+
227241
// We skip checking the last resource condition (available) because it will be covered by checking isRolloutCompleted func.
228242
for i := condition.RolloutStartedCondition; i < condition.TotalCondition-1; i++ {
229243
var oldCond, newCond *metav1.Condition

pkg/controllers/placement/controller_integration_test.go

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import (
3939
"github.com/kubefleet-dev/kubefleet/pkg/utils"
4040
"github.com/kubefleet-dev/kubefleet/pkg/utils/condition"
4141
"github.com/kubefleet-dev/kubefleet/pkg/utils/resource"
42-
statussyncutils "github.com/kubefleet-dev/kubefleet/test/utils/crpstatussync"
42+
"github.com/kubefleet-dev/kubefleet/test/utils/crpstatussync"
4343
metricsUtils "github.com/kubefleet-dev/kubefleet/test/utils/metrics"
4444
)
4545

@@ -2189,26 +2189,31 @@ var _ = Describe("Test ClusterResourcePlacement Controller", func() {
21892189
})
21902190

21912191
It("Should handle missing target namespace", func() {
2192-
By("Validate CRP keeps retrying due to missing namespace (status remain nil)")
2192+
By("Validate CRP status includes StatusSynced condition")
21932193
wantCRP := &placementv1beta1.ClusterResourcePlacement{
21942194
ObjectMeta: metav1.ObjectMeta{
21952195
Name: testCRPName,
21962196
Finalizers: []string{placementv1beta1.PlacementCleanupFinalizer},
21972197
},
21982198
Spec: crp.Spec,
2199+
Status: placementv1beta1.PlacementStatus{
2200+
ObservedResourceIndex: "0",
2201+
Conditions: []metav1.Condition{
2202+
{
2203+
Status: metav1.ConditionUnknown,
2204+
Type: string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
2205+
Reason: condition.SchedulingUnknownReason,
2206+
},
2207+
{
2208+
Status: metav1.ConditionFalse,
2209+
Type: string(placementv1beta1.ClusterResourcePlacementStatusSyncedConditionType),
2210+
Reason: condition.StatusSyncFailedReason,
2211+
},
2212+
},
2213+
},
21992214
}
2200-
retrieveAndValidateClusterResourcePlacement(testCRPName, wantCRP)
2201-
// Ensure that CRP status remains nil as the controller keeps retrying.
2202-
Consistently(func() error {
2203-
gotCRP := &placementv1beta1.ClusterResourcePlacement{}
2204-
if err := k8sClient.Get(ctx, types.NamespacedName{Name: testCRPName}, gotCRP); err != nil {
2205-
return err
2206-
}
2207-
if diff := cmp.Diff(wantCRP, gotCRP, cmpCRPOptions); diff != "" {
2208-
return fmt.Errorf("clusterResourcePlacement mismatch (-want, +got) :\n%s", diff)
2209-
}
2210-
return nil
2211-
}, consistentlyTimeout, interval).Should(Succeed(), "ClusterResourcePlacement status must be nil")
2215+
retrieveAndValidateClusterResourcePlacement(crp.Name, wantCRP)
2216+
22122217
// Namespace doesn't exist, so no CRPS should be created - sanity check.
22132218
By("Ensure no ClusterResourcePlacementStatus is created in the nonexistent namespace")
22142219
Consistently(func() bool {
@@ -2250,7 +2255,7 @@ var _ = Describe("Test ClusterResourcePlacement Controller", func() {
22502255
{
22512256
Group: corev1.GroupName,
22522257
Version: "v1",
2253-
Kind: "InvalidKind", // Invalid kind to trigger user error
2258+
Kind: "InvalidKind", // Invalid kind to trigger user error.
22542259
Name: "invalid-resource",
22552260
},
22562261
},
@@ -2268,7 +2273,7 @@ var _ = Describe("Test ClusterResourcePlacement Controller", func() {
22682273
},
22692274
}
22702275
Expect(k8sClient.Delete(ctx, crp)).Should(Succeed())
2271-
retrieveAndValidateCRPDeletion(gotCRP)
2276+
retrieveAndValidateCRPDeletion(crp)
22722277

22732278
// Need to manually clean up CRPS in test environment https://book.kubebuilder.io/reference/envtest#testing-considerations.
22742279
By("Deleting crps")
@@ -2293,8 +2298,8 @@ var _ = Describe("Test ClusterResourcePlacement Controller", func() {
22932298
Expect(k8sClient.Delete(ctx, namespace)).Should(Succeed())
22942299
})
22952300

2296-
It("Should handle invalid resource selector", func() {
2297-
By("Validate CRP status")
2301+
It("Should handle invalid resource selector and create CRPS", func() {
2302+
By("Validate CRP status includes both Scheduled and StatusSynced conditions")
22982303
wantCRP := &placementv1beta1.ClusterResourcePlacement{
22992304
ObjectMeta: metav1.ObjectMeta{
23002305
Name: testCRPName,
@@ -2303,20 +2308,25 @@ var _ = Describe("Test ClusterResourcePlacement Controller", func() {
23032308
Spec: crp.Spec,
23042309
Status: placementv1beta1.PlacementStatus{
23052310
Conditions: []metav1.Condition{
2311+
// ObservedResourceIndex is not set because resource selector is invalid.
23062312
{
23072313
Status: metav1.ConditionFalse,
23082314
Type: string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
23092315
Reason: condition.InvalidResourceSelectorsReason,
23102316
},
2317+
{
2318+
Status: metav1.ConditionTrue,
2319+
Type: string(placementv1beta1.ClusterResourcePlacementStatusSyncedConditionType),
2320+
Reason: condition.StatusSyncSucceededReason,
2321+
},
23112322
},
23122323
},
23132324
}
2314-
gotCRP = retrieveAndValidateClusterResourcePlacement(crp.Name, wantCRP)
2325+
retrieveAndValidateClusterResourcePlacement(crp.Name, wantCRP)
23152326

2316-
By("Validate CRPS status", func() {
2317-
crpsMatchesActual := statussyncutils.CRPSStatusMatchesCRPActual(ctx, k8sClient, testCRPName, namespaceName)
2318-
Eventually(crpsMatchesActual, eventuallyTimeout, interval).Should(Succeed(), "ClusterResourcePlacementStatus should match expected structure and CRP status")
2319-
})
2327+
By("Validate CRPS is created and matches CRP status")
2328+
crpsMatchesActual := crpstatussync.CRPSStatusMatchesCRPActual(ctx, k8sClient, testCRPName, namespaceName)
2329+
Eventually(crpsMatchesActual, eventuallyTimeout, interval).Should(Succeed(), "ClusterResourcePlacementStatus should be created and match expected structure")
23202330
})
23212331
})
23222332
})

0 commit comments

Comments
 (0)