@@ -11,7 +11,10 @@ import (
1111 "testing"
1212 "time"
1313
14+ appsv1 "k8s.io/api/apps/v1"
1415 k8serrors "k8s.io/apimachinery/pkg/api/errors"
16+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
17+ "k8s.io/apimachinery/pkg/runtime"
1518 "k8s.io/apimachinery/pkg/runtime/schema"
1619
1720 "github.com/google/go-cmp/cmp"
@@ -24,6 +27,7 @@ import (
2427
2528 fleetv1beta1 "go.goms.io/fleet/apis/placement/v1beta1"
2629 "go.goms.io/fleet/pkg/controllers/work"
30+ "go.goms.io/fleet/pkg/utils"
2731 "go.goms.io/fleet/pkg/utils/condition"
2832 "go.goms.io/fleet/pkg/utils/controller"
2933 "go.goms.io/fleet/test/utils/informer"
@@ -143,6 +147,216 @@ func TestGetWorkNamePrefixFromSnapshotName(t *testing.T) {
143147 }
144148}
145149
150+ func TestUpsertWork (t * testing.T ) {
151+ workName := "work"
152+ namespace := "default"
153+
154+ var cmpOptions = []cmp.Option {
155+ // ignore the message as we may change the message in the future
156+ cmpopts .IgnoreFields (fleetv1beta1.Work {}, "Status" ),
157+ cmpopts .IgnoreFields (metav1.ObjectMeta {}, "CreationTimestamp" ),
158+ cmpopts .IgnoreFields (metav1.ObjectMeta {}, "ResourceVersion" ),
159+ cmpopts .IgnoreFields (metav1.ObjectMeta {}, "ManagedFields" ),
160+ cmpopts .IgnoreFields (metav1.Condition {}, "LastTransitionTime" ),
161+ cmpopts .IgnoreFields (fleetv1beta1.WorkloadTemplate {}, "Manifests" ),
162+ }
163+
164+ testDeployment := appsv1.Deployment {
165+ TypeMeta : metav1.TypeMeta {
166+ Kind : utils .DeploymentKind ,
167+ APIVersion : utils .DeploymentGVK .GroupVersion ().String (),
168+ },
169+ ObjectMeta : metav1.ObjectMeta {
170+ Name : "testDeployment" ,
171+ },
172+ Spec : appsv1.DeploymentSpec {
173+ Replicas : ptr .To (int32 (2 )),
174+ MinReadySeconds : 5 ,
175+ },
176+ }
177+ newWork := & fleetv1beta1.Work {
178+ ObjectMeta : metav1.ObjectMeta {
179+ Name : workName ,
180+ Namespace : namespace ,
181+ Labels : map [string ]string {
182+ fleetv1beta1 .ParentResourceSnapshotIndexLabel : "1" ,
183+ },
184+ Annotations : map [string ]string {
185+ fleetv1beta1 .ParentResourceSnapshotNameAnnotation : "snapshot-1" ,
186+ fleetv1beta1 .ParentClusterResourceOverrideSnapshotHashAnnotation : "hash1" ,
187+ fleetv1beta1 .ParentResourceOverrideSnapshotHashAnnotation : "hash2" ,
188+ },
189+ },
190+ Spec : fleetv1beta1.WorkSpec {
191+ Workload : fleetv1beta1.WorkloadTemplate {
192+ Manifests : []fleetv1beta1.Manifest {{RawExtension : runtime.RawExtension {Object : & testDeployment }}},
193+ },
194+ },
195+ }
196+
197+ resourceSnapshot := & fleetv1beta1.ClusterResourceSnapshot {
198+ ObjectMeta : metav1.ObjectMeta {
199+ Name : "snapshot-1" ,
200+ Labels : map [string ]string {
201+ fleetv1beta1 .ResourceIndexLabel : "1" ,
202+ },
203+ },
204+ }
205+
206+ tests := []struct {
207+ name string
208+ existingWork * fleetv1beta1.Work
209+ expectChanged bool
210+ }{
211+ {
212+ name : "Create new work when existing work is nil" ,
213+ existingWork : nil ,
214+ expectChanged : true ,
215+ },
216+ {
217+ name : "Update existing work with new annotations" ,
218+ existingWork : & fleetv1beta1.Work {
219+ ObjectMeta : metav1.ObjectMeta {
220+ Name : workName ,
221+ Namespace : namespace ,
222+ Labels : map [string ]string {
223+ fleetv1beta1 .ParentResourceSnapshotIndexLabel : "1" ,
224+ },
225+ },
226+ Spec : fleetv1beta1.WorkSpec {
227+ Workload : fleetv1beta1.WorkloadTemplate {
228+ Manifests : []fleetv1beta1.Manifest {{RawExtension : runtime.RawExtension {Raw : []byte ("{}" )}}},
229+ },
230+ },
231+ },
232+ expectChanged : true ,
233+ },
234+ {
235+ name : "Update existing work even if it does not have the resource snapshot label" ,
236+ existingWork : & fleetv1beta1.Work {
237+ ObjectMeta : metav1.ObjectMeta {
238+ Name : workName ,
239+ Namespace : namespace ,
240+ },
241+ Spec : fleetv1beta1.WorkSpec {
242+ Workload : fleetv1beta1.WorkloadTemplate {
243+ Manifests : []fleetv1beta1.Manifest {{RawExtension : runtime.RawExtension {Raw : []byte ("{}" )}}},
244+ },
245+ },
246+ },
247+ expectChanged : true ,
248+ },
249+ {
250+ name : "Update existing work if it misses annotations even if the resource snapshot label is correct" ,
251+ existingWork : & fleetv1beta1.Work {
252+ ObjectMeta : metav1.ObjectMeta {
253+ Name : workName ,
254+ Namespace : namespace ,
255+ Labels : map [string ]string {
256+ fleetv1beta1 .ParentResourceSnapshotIndexLabel : "1" ,
257+ },
258+ },
259+ Spec : fleetv1beta1.WorkSpec {
260+ Workload : fleetv1beta1.WorkloadTemplate {
261+ Manifests : []fleetv1beta1.Manifest {{RawExtension : runtime.RawExtension {Raw : []byte ("{}" )}}},
262+ },
263+ },
264+ },
265+ expectChanged : true ,
266+ },
267+ {
268+ name : "Update existing work if it does not have correct override snapshot hash" ,
269+ existingWork : & fleetv1beta1.Work {
270+ ObjectMeta : metav1.ObjectMeta {
271+ Name : workName ,
272+ Namespace : namespace ,
273+ Labels : map [string ]string {
274+ fleetv1beta1 .ParentResourceSnapshotIndexLabel : "1" ,
275+ },
276+ Annotations : map [string ]string {
277+ fleetv1beta1 .ParentResourceSnapshotNameAnnotation : "snapshot-1" ,
278+ fleetv1beta1 .ParentClusterResourceOverrideSnapshotHashAnnotation : "wrong-hash" },
279+ },
280+ Spec : fleetv1beta1.WorkSpec {
281+ Workload : fleetv1beta1.WorkloadTemplate {
282+ Manifests : []fleetv1beta1.Manifest {{RawExtension : runtime.RawExtension {Raw : []byte ("{}" )}}},
283+ },
284+ },
285+ },
286+ expectChanged : true ,
287+ },
288+ {
289+ name : "Do not update the existing work if it already points to the same resource and override snapshots" ,
290+ existingWork : & fleetv1beta1.Work {
291+ ObjectMeta : metav1.ObjectMeta {
292+ Name : workName ,
293+ Namespace : namespace ,
294+ Labels : map [string ]string {
295+ fleetv1beta1 .ParentResourceSnapshotIndexLabel : "1" ,
296+ },
297+ Annotations : map [string ]string {
298+ fleetv1beta1 .ParentResourceSnapshotNameAnnotation : "snapshot-1" ,
299+ fleetv1beta1 .ParentClusterResourceOverrideSnapshotHashAnnotation : "hash1" ,
300+ fleetv1beta1 .ParentResourceOverrideSnapshotHashAnnotation : "hash2" ,
301+ },
302+ },
303+ Spec : fleetv1beta1.WorkSpec {
304+ Workload : fleetv1beta1.WorkloadTemplate {
305+ Manifests : []fleetv1beta1.Manifest {{RawExtension : runtime.RawExtension {Raw : []byte ("{}" )}}},
306+ },
307+ },
308+ },
309+ expectChanged : false ,
310+ },
311+ }
312+
313+ for _ , tt := range tests {
314+ t .Run (tt .name , func (t * testing.T ) {
315+ scheme := serviceScheme (t )
316+ objects := []client.Object {resourceSnapshot }
317+ if tt .existingWork != nil {
318+ objects = append (objects , tt .existingWork )
319+ }
320+ fakeClient := fake .NewClientBuilder ().
321+ WithStatusSubresource (objects ... ).
322+ WithScheme (scheme ).
323+ WithObjects (objects ... ).
324+ Build ()
325+ // Create reconciler with custom client
326+ reconciler := & Reconciler {
327+ Client : fakeClient ,
328+ recorder : record .NewFakeRecorder (10 ),
329+ InformerManager : & informer.FakeManager {},
330+ }
331+ changed , _ := reconciler .upsertWork (ctx , newWork , tt .existingWork , resourceSnapshot )
332+ if changed != tt .expectChanged {
333+ t .Fatalf ("expected changed: %v, got: %v" , tt .expectChanged , changed )
334+ }
335+ upsertedWork := & fleetv1beta1.Work {}
336+ if fakeClient .Get (ctx , client .ObjectKeyFromObject (newWork ), upsertedWork ) != nil {
337+ t .Fatalf ("failed to get upserted work" )
338+ }
339+ if diff := cmp .Diff (newWork , upsertedWork , cmpOptions ... ); diff != "" {
340+ t .Errorf ("upsertWork didn't update the work, mismatch (-want +got):\n %s" , diff )
341+ }
342+ if tt .expectChanged {
343+ // check if the deployment is applied
344+ var u unstructured.Unstructured
345+ if err := u .UnmarshalJSON (upsertedWork .Spec .Workload .Manifests [0 ].Raw ); err != nil {
346+ t .Fatalf ("Failed to unmarshal the result: %v, want nil" , err )
347+ }
348+ var deployment appsv1.Deployment
349+ if err := runtime .DefaultUnstructuredConverter .FromUnstructured (u .Object , & deployment ); err != nil {
350+ t .Fatalf ("Failed to convert the result to deployment: %v, want nil" , err )
351+ }
352+ if diff := cmp .Diff (testDeployment , deployment ); diff != "" {
353+ t .Errorf ("The new Deployment mismatch (-want, +got):\n %s" , diff )
354+ }
355+ }
356+ })
357+ }
358+ }
359+
146360func TestBuildAllWorkAppliedCondition (t * testing.T ) {
147361 tests := map [string ]struct {
148362 works map [string ]* fleetv1beta1.Work
0 commit comments