Skip to content

Commit cfb41a1

Browse files
ryanzhang-ossRyan Zhang
andauthored
feat: make each manifest applied reason clear (#323)
* feat: make each manifest applied reason clear * fix tests Co-authored-by: Ryan Zhang <zhangryan@microsoft.com>
1 parent 32b0f44 commit cfb41a1

File tree

5 files changed

+81
-73
lines changed

5 files changed

+81
-73
lines changed

pkg/controllers/clusterresourceplacement/work_propagation.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ func (r *Reconciler) scheduleWork(ctx context.Context, placement *fleetv1alpha1.
4646
Kind: placement.GroupVersionKind().Kind,
4747
Name: placement.GetName(),
4848
UID: placement.GetUID(),
49-
BlockOwnerDeletion: pointer.BoolPtr(true),
50-
Controller: pointer.BoolPtr(true),
49+
BlockOwnerDeletion: pointer.Bool(true),
50+
Controller: pointer.Bool(true),
5151
}
5252
workerSpec := workv1alpha1.WorkSpec{
5353
Workload: workv1alpha1.WorkloadTemplate{

pkg/controllers/work/apply_controller.go

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,26 @@ func NewApplyWorkReconciler(hubClient client.Client, spokeDynamicClient dynamic.
7676
}
7777
}
7878

79+
// applyAction represents the action we take to apply the manifest
80+
// +enum
81+
type applyAction string
82+
83+
const (
84+
// ManifestCreatedAction indicates that we created the manifest for the first time.
85+
ManifestCreatedAction applyAction = "ManifestCreated"
86+
87+
// ManifestUpdatedAction indicates that we updated the manifest.
88+
ManifestUpdatedAction applyAction = "ManifestUpdated"
89+
90+
// ManifestNoChangeAction indicates that we don't need to change the manifest.
91+
ManifestNoChangeAction applyAction = "ManifestNoChange"
92+
)
93+
7994
// applyResult contains the result of a manifest being applied.
8095
type applyResult struct {
8196
identifier workv1alpha1.ResourceIdentifier
8297
generation int64
83-
updated bool
98+
action applyAction
8499
err error
85100
}
86101

@@ -266,19 +281,16 @@ func (r *ApplyWorkReconciler) applyManifests(ctx context.Context, manifests []wo
266281

267282
default:
268283
addOwnerRef(owner, rawObj)
269-
appliedObj, result.updated, result.err = r.applyUnstructured(ctx, gvr, rawObj)
284+
appliedObj, result.action, result.err = r.applyUnstructured(ctx, gvr, rawObj)
270285
result.identifier = buildResourceIdentifier(index, rawObj, gvr)
271286
logObjRef := klog.ObjectRef{
272287
Name: result.identifier.Name,
273288
Namespace: result.identifier.Namespace,
274289
}
275290
if result.err == nil {
276291
result.generation = appliedObj.GetGeneration()
277-
if result.updated {
278-
klog.V(2).InfoS("manifest upsert succeeded", "gvr", gvr, "manifest", logObjRef, "new ObservedGeneration", result.generation)
279-
} else {
280-
klog.V(2).InfoS("manifest upsert unwarranted", "gvr", gvr, "manifest", logObjRef)
281-
}
292+
klog.V(2).InfoS("apply manifest succeeded", "gvr", gvr, "manifest", logObjRef,
293+
"apply action", result.action, "new ObservedGeneration", result.generation)
282294
} else {
283295
klog.ErrorS(result.err, "manifest upsert failed", "gvr", gvr, "manifest", logObjRef)
284296
}
@@ -305,29 +317,30 @@ func (r *ApplyWorkReconciler) decodeManifest(manifest workv1alpha1.Manifest) (sc
305317
}
306318

307319
// Determines if an unstructured manifest object can & should be applied. If so, it applies (creates) the resource on the cluster.
308-
func (r *ApplyWorkReconciler) applyUnstructured(ctx context.Context, gvr schema.GroupVersionResource, manifestObj *unstructured.Unstructured) (*unstructured.Unstructured, bool, error) {
320+
func (r *ApplyWorkReconciler) applyUnstructured(ctx context.Context, gvr schema.GroupVersionResource,
321+
manifestObj *unstructured.Unstructured) (*unstructured.Unstructured, applyAction, error) {
309322
manifestRef := klog.ObjectRef{
310323
Name: manifestObj.GetName(),
311324
Namespace: manifestObj.GetNamespace(),
312325
}
313326
// compute the hash without taking into consider the last applied annotation
314327
if err := setManifestHashAnnotation(manifestObj); err != nil {
315-
return nil, false, err
328+
return nil, ManifestNoChangeAction, err
316329
}
317330

318331
// extract the common create procedure to reuse
319-
var createFunc = func() (*unstructured.Unstructured, bool, error) {
332+
var createFunc = func() (*unstructured.Unstructured, applyAction, error) {
320333
// record the raw manifest with the hash annotation in the manifest
321334
if err := setModifiedConfigurationAnnotation(manifestObj); err != nil {
322-
return nil, false, err
335+
return nil, ManifestNoChangeAction, err
323336
}
324337
actual, err := r.spokeDynamicClient.Resource(gvr).Namespace(manifestObj.GetNamespace()).Create(
325338
ctx, manifestObj, metav1.CreateOptions{FieldManager: workFieldManagerName})
326339
if err == nil {
327340
klog.V(2).InfoS("successfully created the manifest", "gvr", gvr, "manifest", manifestRef)
328-
return actual, true, nil
341+
return actual, ManifestCreatedAction, nil
329342
}
330-
return nil, false, err
343+
return nil, ManifestNoChangeAction, err
331344
}
332345

333346
// support resources with generated name
@@ -342,27 +355,27 @@ func (r *ApplyWorkReconciler) applyUnstructured(ctx context.Context, gvr schema.
342355
case apierrors.IsNotFound(err):
343356
return createFunc()
344357
case err != nil:
345-
return nil, false, err
358+
return nil, ManifestNoChangeAction, err
346359
}
347360

348361
// check if the existing manifest is managed by the work
349362
if !isManifestManagedByWork(curObj.GetOwnerReferences()) {
350363
err = fmt.Errorf("resource is not managed by the work controller")
351364
klog.ErrorS(err, "skip applying a not managed manifest", "gvr", gvr, "obj", manifestRef)
352-
return nil, false, err
365+
return nil, ManifestNoChangeAction, err
353366
}
354367

355368
// We only try to update the object if its spec hash value has changed.
356369
if manifestObj.GetAnnotations()[manifestHashAnnotation] != curObj.GetAnnotations()[manifestHashAnnotation] {
357370
return r.patchCurrentResource(ctx, gvr, manifestObj, curObj)
358371
}
359372

360-
return curObj, false, nil
373+
return curObj, ManifestNoChangeAction, nil
361374
}
362375

363376
// patchCurrentResource uses three way merge to patch the current resource with the new manifest we get from the work.
364377
func (r *ApplyWorkReconciler) patchCurrentResource(ctx context.Context, gvr schema.GroupVersionResource,
365-
manifestObj, curObj *unstructured.Unstructured) (*unstructured.Unstructured, bool, error) {
378+
manifestObj, curObj *unstructured.Unstructured) (*unstructured.Unstructured, applyAction, error) {
366379
manifestRef := klog.ObjectRef{
367380
Name: manifestObj.GetName(),
368381
Namespace: manifestObj.GetNamespace(),
@@ -376,28 +389,28 @@ func (r *ApplyWorkReconciler) patchCurrentResource(ctx context.Context, gvr sche
376389
manifestObj.SetOwnerReferences(mergeOwnerReference(curObj.GetOwnerReferences(), manifestObj.GetOwnerReferences()))
377390
// record the raw manifest with the hash annotation in the manifest
378391
if err := setModifiedConfigurationAnnotation(manifestObj); err != nil {
379-
return nil, false, err
392+
return nil, ManifestNoChangeAction, err
380393
}
381394
// create the three-way merge patch between the current, original and manifest similar to how kubectl apply does
382395
patch, err := threeWayMergePatch(curObj, manifestObj)
383396
if err != nil {
384397
klog.ErrorS(err, "failed to generate the three way patch", "gvr", gvr, "manifest", manifestRef)
385-
return nil, false, err
398+
return nil, ManifestNoChangeAction, err
386399
}
387400
data, err := patch.Data(manifestObj)
388401
if err != nil {
389402
klog.ErrorS(err, "failed to generate the three way patch", "gvr", gvr, "manifest", manifestRef)
390-
return nil, false, err
403+
return nil, ManifestNoChangeAction, err
391404
}
392405
// Use client side apply the patch to the member cluster
393406
manifestObj, patchErr := r.spokeDynamicClient.Resource(gvr).Namespace(manifestObj.GetNamespace()).
394407
Patch(ctx, manifestObj.GetName(), patch.Type(), data, metav1.PatchOptions{FieldManager: workFieldManagerName})
395408
if patchErr != nil {
396409
klog.ErrorS(patchErr, "failed to patch the manifest", "gvr", gvr, "manifest", manifestRef)
397-
return nil, false, patchErr
410+
return nil, ManifestNoChangeAction, patchErr
398411
}
399412
klog.V(2).InfoS("manifest patch succeeded", "gvr", gvr, "manifest", manifestRef)
400-
return manifestObj, true, nil
413+
return manifestObj, ManifestUpdatedAction, nil
401414
}
402415

403416
// generateWorkCondition constructs the work condition based on the apply result
@@ -409,7 +422,7 @@ func (r *ApplyWorkReconciler) generateWorkCondition(results []applyResult, work
409422
if result.err != nil {
410423
errs = append(errs, result.err)
411424
}
412-
appliedCondition := buildManifestAppliedCondition(result.err, result.updated, result.generation)
425+
appliedCondition := buildManifestAppliedCondition(result.err, result.action, result.generation)
413426
manifestCondition := workv1alpha1.ManifestCondition{
414427
Identifier: result.identifier,
415428
Conditions: []metav1.Condition{appliedCondition},
@@ -577,7 +590,7 @@ func buildResourceIdentifier(index int, object *unstructured.Unstructured, gvr s
577590
}
578591
}
579592

580-
func buildManifestAppliedCondition(err error, updated bool, observedGeneration int64) metav1.Condition {
593+
func buildManifestAppliedCondition(err error, action applyAction, observedGeneration int64) metav1.Condition {
581594
if err != nil {
582595
return metav1.Condition{
583596
Type: ConditionTypeApplied,
@@ -589,27 +602,17 @@ func buildManifestAppliedCondition(err error, updated bool, observedGeneration i
589602
}
590603
}
591604

592-
if updated {
593-
return metav1.Condition{
594-
Type: ConditionTypeApplied,
595-
Status: metav1.ConditionTrue,
596-
LastTransitionTime: metav1.Now(),
597-
ObservedGeneration: observedGeneration,
598-
Reason: "appliedManifestUpdated",
599-
Message: "appliedManifest updated",
600-
}
601-
}
602605
return metav1.Condition{
603606
Type: ConditionTypeApplied,
604607
Status: metav1.ConditionTrue,
605608
LastTransitionTime: metav1.Now(),
606609
ObservedGeneration: observedGeneration,
607-
Reason: "appliedManifestComplete",
608-
Message: "Apply manifest complete",
610+
Reason: string(action),
611+
Message: string(action),
609612
}
610613
}
611614

612-
// generateWorkAppliedCondition generate appied status condition for work.
615+
// generateWorkAppliedCondition generate applied status condition for work.
613616
// If one of the manifests is applied failed on the spoke, the applied status condition of the work is false.
614617
func generateWorkAppliedCondition(manifestConditions []workv1alpha1.ManifestCondition, observedGeneration int64) metav1.Condition {
615618
for _, manifestCond := range manifestConditions {

pkg/controllers/work/apply_controller_test.go

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ func TestApplyUnstructured(t *testing.T) {
350350
reconciler ApplyWorkReconciler
351351
workObj *unstructured.Unstructured
352352
resultSpecHash string
353-
resultBool bool
353+
resultAction applyAction
354354
resultErr error
355355
}{
356356
"test creation succeeds when the object does not exist": {
@@ -363,7 +363,7 @@ func TestApplyUnstructured(t *testing.T) {
363363
},
364364
workObj: correctObj.DeepCopy(),
365365
resultSpecHash: correctSpecHash,
366-
resultBool: true,
366+
resultAction: ManifestCreatedAction,
367367
resultErr: nil,
368368
},
369369
"test creation succeeds when the object has a generated name": {
@@ -376,7 +376,7 @@ func TestApplyUnstructured(t *testing.T) {
376376
},
377377
workObj: generatedSpecObj.DeepCopy(),
378378
resultSpecHash: generatedSpecHash,
379-
resultBool: true,
379+
resultAction: ManifestCreatedAction,
380380
resultErr: nil,
381381
},
382382
"client error looking for object / fail": {
@@ -387,9 +387,9 @@ func TestApplyUnstructured(t *testing.T) {
387387
restMapper: testMapper{},
388388
recorder: utils.NewFakeRecorder(1),
389389
},
390-
workObj: correctObj.DeepCopy(),
391-
resultBool: false,
392-
resultErr: errors.New("client error"),
390+
workObj: correctObj.DeepCopy(),
391+
resultAction: ManifestNoChangeAction,
392+
resultErr: errors.New("client error"),
393393
},
394394
"owner reference comparison failure / fail": {
395395
reconciler: ApplyWorkReconciler{
@@ -399,9 +399,9 @@ func TestApplyUnstructured(t *testing.T) {
399399
restMapper: testMapper{},
400400
recorder: utils.NewFakeRecorder(1),
401401
},
402-
workObj: correctObj.DeepCopy(),
403-
resultBool: false,
404-
resultErr: errors.New("resource is not managed by the work controller"),
402+
workObj: correctObj.DeepCopy(),
403+
resultAction: ManifestNoChangeAction,
404+
resultErr: errors.New("resource is not managed by the work controller"),
405405
},
406406
"equal spec hash of current vs work object / succeed without updates": {
407407
reconciler: ApplyWorkReconciler{
@@ -410,17 +410,17 @@ func TestApplyUnstructured(t *testing.T) {
410410
},
411411
workObj: correctObj.DeepCopy(),
412412
resultSpecHash: correctSpecHash,
413-
resultBool: false,
413+
resultAction: ManifestNoChangeAction,
414414
resultErr: nil,
415415
},
416416
"unequal spec hash of current vs work object / client patch fail": {
417417
reconciler: ApplyWorkReconciler{
418418
spokeDynamicClient: patchFailClient,
419419
recorder: utils.NewFakeRecorder(1),
420420
},
421-
workObj: correctObj.DeepCopy(),
422-
resultBool: false,
423-
resultErr: errors.New("patch failed"),
421+
workObj: correctObj.DeepCopy(),
422+
resultAction: ManifestNoChangeAction,
423+
resultErr: errors.New("patch failed"),
424424
},
425425
"happy path - with updates": {
426426
reconciler: ApplyWorkReconciler{
@@ -430,15 +430,15 @@ func TestApplyUnstructured(t *testing.T) {
430430
},
431431
workObj: correctObj,
432432
resultSpecHash: diffSpecHash,
433-
resultBool: true,
433+
resultAction: ManifestUpdatedAction,
434434
resultErr: nil,
435435
},
436436
}
437437

438438
for testName, testCase := range testCases {
439439
t.Run(testName, func(t *testing.T) {
440-
applyResult, applyResultBool, err := testCase.reconciler.applyUnstructured(context.Background(), testGvr, testCase.workObj)
441-
assert.Equalf(t, testCase.resultBool, applyResultBool, "updated boolean not matching for Testcase %s", testName)
440+
applyResult, applyAction, err := testCase.reconciler.applyUnstructured(context.Background(), testGvr, testCase.workObj)
441+
assert.Equalf(t, testCase.resultAction, applyAction, "updated boolean not matching for Testcase %s", testName)
442442
if testCase.resultErr != nil {
443443
assert.Containsf(t, err.Error(), testCase.resultErr.Error(), "error not matching for Testcase %s", testName)
444444
} else {
@@ -488,7 +488,7 @@ func TestApplyManifest(t *testing.T) {
488488
reconciler ApplyWorkReconciler
489489
manifestList []workv1alpha1.Manifest
490490
generation int64
491-
updated bool
491+
action applyAction
492492
wantGvr schema.GroupVersionResource
493493
wantErr error
494494
}{
@@ -501,9 +501,9 @@ func TestApplyManifest(t *testing.T) {
501501
recorder: utils.NewFakeRecorder(1),
502502
joined: atomic.NewBool(true),
503503
},
504-
manifestList: append([]workv1alpha1.Manifest{}, testManifest),
504+
manifestList: []workv1alpha1.Manifest{testManifest},
505505
generation: 0,
506-
updated: true,
506+
action: ManifestCreatedAction,
507507
wantGvr: expectedGvr,
508508
wantErr: nil,
509509
},
@@ -518,7 +518,7 @@ func TestApplyManifest(t *testing.T) {
518518
},
519519
manifestList: append([]workv1alpha1.Manifest{}, InvalidManifest),
520520
generation: 0,
521-
updated: false,
521+
action: ManifestNoChangeAction,
522522
wantGvr: emptyGvr,
523523
wantErr: &json.UnmarshalTypeError{
524524
Value: "string",
@@ -536,7 +536,7 @@ func TestApplyManifest(t *testing.T) {
536536
},
537537
manifestList: append([]workv1alpha1.Manifest{}, MissingManifest),
538538
generation: 0,
539-
updated: false,
539+
action: ManifestNoChangeAction,
540540
wantGvr: emptyGvr,
541541
wantErr: errors.New("failed to find group/version/resource from restmapping: test error: mapping does not exist"),
542542
},
@@ -551,7 +551,7 @@ func TestApplyManifest(t *testing.T) {
551551
},
552552
manifestList: append([]workv1alpha1.Manifest{}, testManifest),
553553
generation: 0,
554-
updated: false,
554+
action: ManifestNoChangeAction,
555555
wantGvr: expectedGvr,
556556
wantErr: errors.New(failMsg),
557557
},
@@ -563,9 +563,10 @@ func TestApplyManifest(t *testing.T) {
563563
for _, result := range resultList {
564564
if testCase.wantErr != nil {
565565
assert.Containsf(t, result.err.Error(), testCase.wantErr.Error(), "Incorrect error for Testcase %s", testName)
566+
} else {
567+
assert.Equalf(t, testCase.generation, result.generation, "Testcase %s: generation incorrect", testName)
568+
assert.Equalf(t, testCase.action, result.action, "Testcase %s: Updated action incorrect", testName)
566569
}
567-
assert.Equalf(t, testCase.generation, result.generation, "Testcase %s: generation incorrect", testName)
568-
assert.Equalf(t, testCase.updated, result.updated, "Testcase %s: Updated boolean incorrect", testName)
569570
}
570571
})
571572
}

0 commit comments

Comments
 (0)