Skip to content

Commit 0fc34ff

Browse files
authored
feat: override triggers rollout (#993)
1 parent 5e20a42 commit 0fc34ff

File tree

3 files changed

+227
-3
lines changed

3 files changed

+227
-3
lines changed

pkg/controllers/rollout/controller.go

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,26 @@ func (r *Reconciler) SetupWithManager(mgr runtime.Manager) error {
626626
handleResourceSnapshot(e.Object, q)
627627
},
628628
}).
629+
Watches(&fleetv1alpha1.ClusterResourceOverrideSnapshot{}, handler.Funcs{
630+
CreateFunc: func(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) {
631+
klog.V(2).InfoS("Handling a clusterResourceOverrideSnapshot create event", "clusterResourceOverrideSnapshot", klog.KObj(e.Object))
632+
handleClusterResourceOverrideSnapshot(e.Object, q)
633+
},
634+
GenericFunc: func(ctx context.Context, e event.GenericEvent, q workqueue.RateLimitingInterface) {
635+
klog.V(2).InfoS("Handling a clusterResourceOverrideSnapshot generic event", "clusterResourceOverrideSnapshot", klog.KObj(e.Object))
636+
handleClusterResourceOverrideSnapshot(e.Object, q)
637+
},
638+
}).
639+
Watches(&fleetv1alpha1.ResourceOverrideSnapshot{}, handler.Funcs{
640+
CreateFunc: func(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) {
641+
klog.V(2).InfoS("Handling a resourceOverrideSnapshot create event", "resourceOverrideSnapshot", klog.KObj(e.Object))
642+
handleResourceOverrideSnapshot(e.Object, q)
643+
},
644+
GenericFunc: func(ctx context.Context, e event.GenericEvent, q workqueue.RateLimitingInterface) {
645+
klog.V(2).InfoS("Handling a resourceOverrideSnapshot generic event", "resourceOverrideSnapshot", klog.KObj(e.Object))
646+
handleResourceOverrideSnapshot(e.Object, q)
647+
},
648+
}).
629649
Watches(&fleetv1beta1.ClusterResourceBinding{}, handler.Funcs{
630650
CreateFunc: func(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) {
631651
klog.V(2).InfoS("Handling a resourceBinding create event", "resourceBinding", klog.KObj(e.Object))
@@ -643,6 +663,72 @@ func (r *Reconciler) SetupWithManager(mgr runtime.Manager) error {
643663
Complete(r)
644664
}
645665

666+
// handleClusterResourceOverrideSnapshot parse the clusterResourceOverrideSnapshot label and enqueue the CRP name associated
667+
// with the clusterResourceOverrideSnapshot if set.
668+
func handleClusterResourceOverrideSnapshot(o client.Object, q workqueue.RateLimitingInterface) {
669+
snapshot, ok := o.(*fleetv1alpha1.ClusterResourceOverrideSnapshot)
670+
if !ok {
671+
klog.ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("non ClusterResourceOverrideSnapshot type resource: %+v", o)),
672+
"Rollout controller received invalid ClusterResourceOverrideSnapshot event", "object", klog.KObj(o))
673+
return
674+
}
675+
676+
snapshotKRef := klog.KObj(snapshot)
677+
// check if it is the latest resource resourceBinding
678+
isLatest, err := strconv.ParseBool(snapshot.GetLabels()[fleetv1beta1.IsLatestSnapshotLabel])
679+
if err != nil {
680+
klog.ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("invalid label value %s : %w", fleetv1beta1.IsLatestSnapshotLabel, err)),
681+
"Resource clusterResourceOverrideSnapshot has does not have a valid islatest label", "clusterResourceOverrideSnapshot", snapshotKRef)
682+
return
683+
}
684+
if !isLatest {
685+
// All newly created resource snapshots should start with the latest label to be true.
686+
// However, this can happen if the label is removed fast by the time this reconcile loop is triggered.
687+
klog.V(2).InfoS("Newly created resource clusterResourceOverrideSnapshot %s is not the latest", "clusterResourceOverrideSnapshot", snapshotKRef)
688+
return
689+
}
690+
if snapshot.Spec.OverrideSpec.Placement == nil {
691+
return
692+
}
693+
// enqueue the CRP to the rollout controller queue
694+
q.Add(reconcile.Request{
695+
NamespacedName: types.NamespacedName{Name: snapshot.Spec.OverrideSpec.Placement.Name},
696+
})
697+
}
698+
699+
// handleResourceOverrideSnapshot parse the resourceOverrideSnapshot label and enqueue the CRP name associated with the
700+
// resourceOverrideSnapshot if set.
701+
func handleResourceOverrideSnapshot(o client.Object, q workqueue.RateLimitingInterface) {
702+
snapshot, ok := o.(*fleetv1alpha1.ResourceOverrideSnapshot)
703+
if !ok {
704+
klog.ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("non ResourceOverrideSnapshot type resource: %+v", o)),
705+
"Rollout controller received invalid ResourceOverrideSnapshot event", "object", klog.KObj(o))
706+
return
707+
}
708+
709+
snapshotKRef := klog.KObj(snapshot)
710+
// check if it is the latest resource resourceBinding
711+
isLatest, err := strconv.ParseBool(snapshot.GetLabels()[fleetv1beta1.IsLatestSnapshotLabel])
712+
if err != nil {
713+
klog.ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("invalid label value %s : %w", fleetv1beta1.IsLatestSnapshotLabel, err)),
714+
"Resource resourceOverrideSnapshot has does not have a valid islatest annotation", "resourceOverrideSnapshot", snapshotKRef)
715+
return
716+
}
717+
if !isLatest {
718+
// All newly created resource snapshots should start with the latest label to be true.
719+
// However, this can happen if the label is removed fast by the time this reconcile loop is triggered.
720+
klog.V(2).InfoS("Newly created resource resourceOverrideSnapshot %s is not the latest", "resourceOverrideSnapshot", snapshotKRef)
721+
return
722+
}
723+
if snapshot.Spec.OverrideSpec.Placement == nil {
724+
return
725+
}
726+
// enqueue the CRP to the rollout controller queue
727+
q.Add(reconcile.Request{
728+
NamespacedName: types.NamespacedName{Name: snapshot.Spec.OverrideSpec.Placement.Name},
729+
})
730+
}
731+
646732
// handleResourceSnapshot parse the resourceBinding label and annotation and enqueue the CRP name associated with the resource resourceBinding
647733
func handleResourceSnapshot(snapshot client.Object, q workqueue.RateLimitingInterface) {
648734
snapshotKRef := klog.KObj(snapshot)
@@ -656,14 +742,14 @@ func handleResourceSnapshot(snapshot client.Object, q workqueue.RateLimitingInte
656742
// check if it is the latest resource resourceBinding
657743
isLatest, err := strconv.ParseBool(snapshot.GetLabels()[fleetv1beta1.IsLatestSnapshotLabel])
658744
if err != nil {
659-
klog.ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("invalid annotation value %s : %w", fleetv1beta1.IsLatestSnapshotLabel, err)),
660-
"Resource resourceBinding has does not have a valid islatest annotation", "clusterResourceSnapshot", snapshotKRef)
745+
klog.ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("invalid label value %s : %w", fleetv1beta1.IsLatestSnapshotLabel, err)),
746+
"Resource clusterResourceSnapshot has does not have a valid islatest annotation", "clusterResourceSnapshot", snapshotKRef)
661747
return
662748
}
663749
if !isLatest {
664750
// All newly created resource snapshots should start with the latest label to be true.
665751
// However, this can happen if the label is removed fast by the time this reconcile loop is triggered.
666-
klog.V(2).InfoS("Newly created resource resourceBinding %s is not the latest", "clusterResourceSnapshot", snapshotKRef)
752+
klog.V(2).InfoS("Newly created resource clusterResourceSnapshot %s is not the latest", "clusterResourceSnapshot", snapshotKRef)
667753
return
668754
}
669755
// get the CRP name from the label

test/e2e/placement_cro_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,54 @@ var _ = Describe("creating clusterResourceOverride (selecting all clusters) to o
9393
want := map[string]string{croTestAnnotationKey: croTestAnnotationValue}
9494
checkIfOverrideAnnotationsOnAllMemberClusters(true, want)
9595
})
96+
97+
It("update cro attached to this CRP only and change annotation value", func() {
98+
Eventually(func() error {
99+
cro := &placementv1alpha1.ClusterResourceOverride{}
100+
if err := hubClient.Get(ctx, types.NamespacedName{Name: croName}, cro); err != nil {
101+
return err
102+
}
103+
cro.Spec = placementv1alpha1.ClusterResourceOverrideSpec{
104+
Placement: &placementv1alpha1.PlacementRef{
105+
Name: crpName, // assigned CRP name
106+
},
107+
ClusterResourceSelectors: workResourceSelector(),
108+
Policy: &placementv1alpha1.OverridePolicy{
109+
OverrideRules: []placementv1alpha1.OverrideRule{
110+
{
111+
ClusterSelector: &placementv1beta1.ClusterSelector{
112+
ClusterSelectorTerms: []placementv1beta1.ClusterSelectorTerm{},
113+
},
114+
JSONPatchOverrides: []placementv1alpha1.JSONPatchOverride{
115+
{
116+
Operator: placementv1alpha1.JSONPatchOverrideOpAdd,
117+
Path: "/metadata/annotations",
118+
// changed the annotation value to croTestAnnotationValue1
119+
Value: apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf(`{"%s": "%s"}`, croTestAnnotationKey, croTestAnnotationValue1))},
120+
},
121+
},
122+
},
123+
},
124+
},
125+
}
126+
return hubClient.Update(ctx, cro)
127+
}, eventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update cro as expected", crpName)
128+
})
129+
130+
It("should update CRP status as expected", func() {
131+
wantCRONames := []string{fmt.Sprintf(placementv1alpha1.OverrideSnapshotNameFmt, croName, 1)}
132+
crpStatusUpdatedActual := crpStatusWithOverrideUpdatedActual(workResourceIdentifiers(), allMemberClusterNames, "0", wantCRONames, nil)
133+
// use the long duration to wait until the rollout is finished.
134+
Eventually(crpStatusUpdatedActual, longEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update CRP %s status as expected", crpName)
135+
})
136+
137+
// This check will ignore the annotation of resources.
138+
It("should place the selected resources on member clusters", checkIfPlacedWorkResourcesOnAllMemberClusters)
139+
140+
It("should have new override annotation value on the placed resources", func() {
141+
want := map[string]string{croTestAnnotationKey: croTestAnnotationValue1}
142+
checkIfOverrideAnnotationsOnAllMemberClusters(true, want)
143+
})
96144
})
97145

98146
var _ = Describe("creating clusterResourceOverride with multiple jsonPatchOverrides", Ordered, func() {
@@ -173,6 +221,25 @@ var _ = Describe("creating clusterResourceOverride with multiple jsonPatchOverri
173221
wantAnnotations := map[string]string{croTestAnnotationKey: croTestAnnotationValue, croTestAnnotationKey1: croTestAnnotationValue1}
174222
checkIfOverrideAnnotationsOnAllMemberClusters(true, wantAnnotations)
175223
})
224+
225+
It("update cro attached to an invalid CRP", func() {
226+
Eventually(func() error {
227+
cro := &placementv1alpha1.ClusterResourceOverride{}
228+
if err := hubClient.Get(ctx, types.NamespacedName{Name: croName}, cro); err != nil {
229+
return err
230+
}
231+
cro.Spec.Placement = &placementv1alpha1.PlacementRef{
232+
Name: "invalid-crp", // assigned CRP name
233+
}
234+
return hubClient.Update(ctx, cro)
235+
}, eventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update cro as expected", crpName)
236+
})
237+
238+
It("CRP status should not be changed", func() {
239+
wantCRONames := []string{fmt.Sprintf(placementv1alpha1.OverrideSnapshotNameFmt, croName, 0)}
240+
crpStatusUpdatedActual := crpStatusWithOverrideUpdatedActual(workResourceIdentifiers(), allMemberClusterNames, "0", wantCRONames, nil)
241+
Consistently(crpStatusUpdatedActual, consistentlyDuration, consistentlyInterval).Should(Succeed(), "CRP %s status has been changed", crpName)
242+
})
176243
})
177244

178245
var _ = Describe("creating clusterResourceOverride with different rules for each cluster", Ordered, func() {

test/e2e/placement_ro_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,55 @@ var _ = Describe("creating resourceOverride (selecting all clusters) to override
9797
want := map[string]string{roTestAnnotationKey: roTestAnnotationValue}
9898
checkIfOverrideAnnotationsOnAllMemberClusters(false, want)
9999
})
100+
101+
It("update ro attached to this CRP only and change annotation value", func() {
102+
Eventually(func() error {
103+
ro := &placementv1alpha1.ResourceOverride{}
104+
if err := hubClient.Get(ctx, types.NamespacedName{Name: roName, Namespace: roNamespace}, ro); err != nil {
105+
return err
106+
}
107+
ro.Spec = placementv1alpha1.ResourceOverrideSpec{
108+
Placement: &placementv1alpha1.PlacementRef{
109+
Name: crpName,
110+
},
111+
ResourceSelectors: configMapSelector(),
112+
Policy: &placementv1alpha1.OverridePolicy{
113+
OverrideRules: []placementv1alpha1.OverrideRule{
114+
{
115+
ClusterSelector: &placementv1beta1.ClusterSelector{
116+
ClusterSelectorTerms: []placementv1beta1.ClusterSelectorTerm{},
117+
},
118+
JSONPatchOverrides: []placementv1alpha1.JSONPatchOverride{
119+
{
120+
Operator: placementv1alpha1.JSONPatchOverrideOpAdd,
121+
Path: "/metadata/annotations",
122+
Value: apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf(`{"%s": "%s"}`, roTestAnnotationKey, roTestAnnotationValue1))},
123+
},
124+
},
125+
},
126+
},
127+
},
128+
}
129+
return hubClient.Update(ctx, ro)
130+
}, eventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update ro as expected", crpName)
131+
})
132+
133+
It("should update CRP status as expected", func() {
134+
wantRONames := []placementv1beta1.NamespacedName{
135+
{Namespace: roNamespace, Name: fmt.Sprintf(placementv1alpha1.OverrideSnapshotNameFmt, roName, 1)},
136+
}
137+
crpStatusUpdatedActual := crpStatusWithOverrideUpdatedActual(workResourceIdentifiers(), allMemberClusterNames, "0", nil, wantRONames)
138+
// use the long duration to wait until the rollout is finished.
139+
Eventually(crpStatusUpdatedActual, longEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update CRP %s status as expected", crpName)
140+
})
141+
142+
// This check will ignore the annotation of resources.
143+
It("should place the selected resources on member clusters", checkIfPlacedWorkResourcesOnAllMemberClusters)
144+
145+
It("should have override annotations on the configmap", func() {
146+
want := map[string]string{roTestAnnotationKey: roTestAnnotationValue1}
147+
checkIfOverrideAnnotationsOnAllMemberClusters(false, want)
148+
})
100149
})
101150

102151
var _ = Describe("creating resourceOverride with multiple jsonPatchOverrides to override configMap", Ordered, func() {
@@ -181,6 +230,28 @@ var _ = Describe("creating resourceOverride with multiple jsonPatchOverrides to
181230
wantAnnotations := map[string]string{roTestAnnotationKey: roTestAnnotationValue, roTestAnnotationKey1: roTestAnnotationValue1}
182231
checkIfOverrideAnnotationsOnAllMemberClusters(false, wantAnnotations)
183232
})
233+
234+
It("update ro attached to an invalid CRP", func() {
235+
Eventually(func() error {
236+
ro := &placementv1alpha1.ResourceOverride{}
237+
if err := hubClient.Get(ctx, types.NamespacedName{Name: roName, Namespace: roNamespace}, ro); err != nil {
238+
return err
239+
}
240+
ro.Spec.Placement = &placementv1alpha1.PlacementRef{
241+
Name: "invalid-crp", // assigned CRP name
242+
}
243+
return hubClient.Update(ctx, ro)
244+
}, eventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update ro as expected", crpName)
245+
})
246+
247+
It("CRP status should not be changed", func() {
248+
wantRONames := []placementv1beta1.NamespacedName{
249+
{Namespace: roNamespace, Name: fmt.Sprintf(placementv1alpha1.OverrideSnapshotNameFmt, roName, 0)},
250+
}
251+
crpStatusUpdatedActual := crpStatusWithOverrideUpdatedActual(workResourceIdentifiers(), allMemberClusterNames, "0", nil, wantRONames)
252+
Consistently(crpStatusUpdatedActual, consistentlyDuration, consistentlyInterval).Should(Succeed(), "CRP %s status has been changed", crpName)
253+
})
254+
184255
})
185256

186257
var _ = Describe("creating resourceOverride with different rules for each cluster to override configMap", Ordered, func() {

0 commit comments

Comments
 (0)