Skip to content

Commit 2629d57

Browse files
committed
cache/replication: fix comparison whether update is needed by removing meta field
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
1 parent e8bc945 commit 2629d57

File tree

3 files changed

+87
-42
lines changed

3 files changed

+87
-42
lines changed

pkg/reconciler/cache/replication/replication_reconcile.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,11 @@ func (r *reconciler) reconcile(ctx context.Context, key string) error {
184184
}
185185

186186
// update global copy and compare
187-
metaChanged, err := ensureMeta(globalCopy, localCopy)
187+
metaChanged, metaReason, err := ensureMeta(globalCopy, localCopy)
188188
if err != nil {
189189
return err
190190
}
191-
remainingChanged, err := ensureRemaining(globalCopy, localCopy)
191+
remainingChanged, remainingReason, err := ensureRemaining(globalCopy, localCopy)
192192
if err != nil {
193193
return err
194194
}
@@ -197,7 +197,14 @@ func (r *reconciler) reconcile(ctx context.Context, key string) error {
197197
return nil
198198
}
199199

200-
logger.V(2).WithValues("kind", globalCopy.GetKind(), "namespace", globalCopy.GetNamespace(), "name", globalCopy.GetName()).Info("Updating object in global cache")
200+
reason := metaReason
201+
if metaChanged && remainingChanged {
202+
reason = fmt.Sprintf("%s; %s", metaReason, remainingReason)
203+
} else if remainingChanged {
204+
reason = remainingReason
205+
}
206+
207+
logger.V(2).WithValues("kind", globalCopy.GetKind(), "namespace", globalCopy.GetNamespace(), "name", globalCopy.GetName(), "reason", reason).Info("Updating object in global cache")
201208
_, err = r.updateObject(ctx, clusterName, globalCopy) // no need for patch because there is only this actor
202209
return err
203210
}

pkg/reconciler/cache/replication/replication_reconcile_unstructured.go

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,57 +17,72 @@ limitations under the License.
1717
package replication
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"reflect"
2223

24+
"github.com/google/go-cmp/cmp"
25+
2326
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2427
"k8s.io/apimachinery/pkg/runtime"
2528
genericrequest "k8s.io/apiserver/pkg/endpoints/request"
29+
"k8s.io/klog/v2"
2630
)
2731

2832
// ensureMeta changes unstructuredCacheObject's metadata to match unstructuredLocalObject's metadata except the ResourceVersion and the shard annotation fields.
29-
func ensureMeta(cacheObject *unstructured.Unstructured, localObject *unstructured.Unstructured) (changed bool, err error) {
33+
func ensureMeta(cacheObject *unstructured.Unstructured, localObject *unstructured.Unstructured) (changed bool, reason string, err error) {
3034
cacheObjMetaRaw, hasCacheObjMetaRaw, err := unstructured.NestedFieldNoCopy(cacheObject.Object, "metadata")
3135
if err != nil {
32-
return false, err
36+
return false, "", err
3337
}
3438
cacheObjMeta, ok := cacheObjMetaRaw.(map[string]interface{})
3539
if !ok {
36-
return false, fmt.Errorf("metadata field of unstructuredCacheObject is of the type %T, expected map[string]interface{}", cacheObjMetaRaw)
40+
return false, "", fmt.Errorf("metadata field of unstructuredCacheObject is of the type %T, expected map[string]interface{}", cacheObjMetaRaw)
3741
}
3842
localObjMetaRaw, hasLocalObjMetaRaw, err := unstructured.NestedFieldNoCopy(localObject.Object, "metadata")
3943
if err != nil {
40-
return false, err
44+
return false, "", err
4145
}
4246
localObjMeta, ok := localObjMetaRaw.(map[string]interface{})
4347
if !ok {
44-
return false, fmt.Errorf("metadata field of unstructuredLocalObjectMeta is of the type %T, expected map[string]interface{}", localObjMetaRaw)
48+
return false, "", fmt.Errorf("metadata field of unstructuredLocalObjectMeta is of the type %T, expected map[string]interface{}", localObjMetaRaw)
4549
}
4650
if !hasLocalObjMetaRaw && !hasCacheObjMetaRaw {
47-
return false, nil // no-op
51+
return false, "", nil // no-op
4852
}
4953
if !hasLocalObjMetaRaw {
5054
unstructured.RemoveNestedField(cacheObject.Object, "metadata")
51-
return true, nil
55+
return true, "local .metadata disappeared", nil
5256
}
5357

54-
// before we can compare the cache object we need to
55-
// store, remove and then bring back fields that are unique only to the cache object
56-
if cacheObjRV, found := cacheObjMeta["resourceVersion"]; found {
57-
unstructured.RemoveNestedField(cacheObjMeta, "resourceVersion")
58-
defer func() {
59-
if err == nil {
60-
err = unstructured.SetNestedField(cacheObject.Object, cacheObjRV, "metadata", "resourceVersion")
61-
}
62-
}()
58+
// before we can compare the local and cache objects we need to remove certain
59+
// field we know will be different, and bring them back after the comparison.
60+
for _, pth := range []string{"resourceVersion", "generation", "managedFields"} {
61+
pth := pth
62+
if v, found := cacheObjMeta[pth]; found {
63+
delete(cacheObjMeta, pth)
64+
defer func() { //nolint:gocritic
65+
if err == nil {
66+
err = unstructured.SetNestedField(cacheObject.Object, v, "metadata", pth)
67+
}
68+
}()
69+
}
70+
if v, found := localObjMeta[pth]; found {
71+
delete(localObjMeta, pth)
72+
defer func() { //nolint:gocritic
73+
if err == nil {
74+
err = unstructured.SetNestedField(localObject.Object, v, "metadata", pth)
75+
}
76+
}()
77+
}
6378
}
6479
if cacheObjAnnotationsRaw, found := cacheObjMeta["annotations"]; found {
6580
cacheObjAnnotations, ok := cacheObjAnnotationsRaw.(map[string]interface{})
6681
if !ok {
67-
return false, fmt.Errorf("metadata.annotations field of unstructuredCacheObject is of the type %T, expected map[string]interface{}", cacheObjAnnotationsRaw)
82+
return false, "", fmt.Errorf("metadata.annotations field of unstructuredCacheObject is of the type %T, expected map[string]interface{}", cacheObjAnnotationsRaw)
6883
}
6984
if shard, hasShard := cacheObjAnnotations[genericrequest.ShardAnnotationKey]; hasShard {
70-
unstructured.RemoveNestedField(cacheObjAnnotations, genericrequest.ShardAnnotationKey)
85+
delete(cacheObjAnnotations, genericrequest.ShardAnnotationKey)
7186
defer func() {
7287
if err == nil {
7388
err = unstructured.SetNestedField(cacheObject.Object, shard, "metadata", "annotations", genericrequest.ShardAnnotationKey)
@@ -77,35 +92,29 @@ func ensureMeta(cacheObject *unstructured.Unstructured, localObject *unstructure
7792
// TODO: in the future the original RV will be stored in an annotation
7893
}
7994

80-
// before we can compare with the local object we need to
81-
// store, remove and then bring back the ResourceVersion on the local object
82-
if localObjRV, found := localObjMeta["resourceVersion"]; found {
83-
unstructured.RemoveNestedField(localObjMeta, "resourceVersion")
84-
defer func() {
85-
if err == nil {
86-
localObjMeta["resourceVersion"] = localObjRV
87-
}
88-
}()
89-
}
90-
9195
changed = !reflect.DeepEqual(cacheObjMeta, localObjMeta)
9296
if !changed {
93-
return false, nil
97+
return false, "", nil
98+
}
99+
100+
reason = ".metadata changed"
101+
if klog.FromContext(context.Background()).V(5).Enabled() {
102+
reason += " " + cmp.Diff(localObjMeta, cacheObjMeta)
94103
}
95104

96105
newCacheObjMeta := map[string]interface{}{}
97106
for k, v := range localObjMeta {
98107
newCacheObjMeta[k] = v
99108
}
100-
return true, unstructured.SetNestedMap(cacheObject.Object, newCacheObjMeta, "metadata")
109+
return true, reason, unstructured.SetNestedMap(cacheObject.Object, newCacheObjMeta, "metadata")
101110
}
102111

103112
// ensureRemaining changes unstructuredCacheObject to match unstructuredLocalObject except for the metadata field
104113
// returns true when the unstructuredCacheObject was updated.
105-
func ensureRemaining(cacheObject *unstructured.Unstructured, localObject *unstructured.Unstructured) (bool, error) {
114+
func ensureRemaining(cacheObject *unstructured.Unstructured, localObject *unstructured.Unstructured) (bool, string, error) {
106115
cacheObjMeta, found, err := unstructured.NestedFieldNoCopy(cacheObject.Object, "metadata")
107116
if err != nil {
108-
return false, err
117+
return false, "", err
109118
}
110119
if found {
111120
unstructured.RemoveNestedField(cacheObject.Object, "metadata")
@@ -116,7 +125,7 @@ func ensureRemaining(cacheObject *unstructured.Unstructured, localObject *unstru
116125

117126
localObjMeta, found, err := unstructured.NestedFieldNoCopy(localObject.Object, "metadata")
118127
if err != nil {
119-
return false, err
128+
return false, "", err
120129
}
121130
if found {
122131
unstructured.RemoveNestedField(localObject.Object, "metadata")
@@ -127,15 +136,20 @@ func ensureRemaining(cacheObject *unstructured.Unstructured, localObject *unstru
127136

128137
changed := !reflect.DeepEqual(cacheObject.Object, localObject.Object)
129138
if !changed {
130-
return false, nil
139+
return false, "", nil
140+
}
141+
142+
reason := "object changed"
143+
if klog.FromContext(context.Background()).V(5).Enabled() {
144+
reason += " " + cmp.Diff(localObject.Object, cacheObject.Object)
131145
}
132146

133147
newCacheObj := map[string]interface{}{}
134148
for k, v := range localObject.Object {
135149
newCacheObj[k] = v
136150
}
137151
cacheObject.Object = newCacheObj
138-
return true, nil
152+
return true, reason, nil
139153
}
140154

141155
func toUnstructured(obj interface{}) (*unstructured.Unstructured, error) {

pkg/reconciler/cache/replication/replication_reconcile_unstructured_test.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,34 @@ func TestEnsureUnstructuredSpec(t *testing.T) {
119119
}
120120
},
121121
},
122+
{
123+
name: "generation on local different than cache",
124+
cacheObject: &unstructured.Unstructured{Object: map[string]interface{}{
125+
"spec": map[string]interface{}{"fieldA": "a"},
126+
"metadata": map[string]interface{}{"generation": int64(1)},
127+
}},
128+
localObject: &unstructured.Unstructured{Object: map[string]interface{}{
129+
"spec": map[string]interface{}{"fieldA": "a"},
130+
"metadata": map[string]interface{}{"generation": int64(2)},
131+
}},
132+
expectSpecChanged: false,
133+
},
134+
{
135+
name: "managedFields on local different than cache",
136+
cacheObject: &unstructured.Unstructured{Object: map[string]interface{}{
137+
"spec": map[string]interface{}{"fieldA": "a"},
138+
"metadata": map[string]interface{}{"managedFields": []interface{}{"a"}},
139+
}},
140+
localObject: &unstructured.Unstructured{Object: map[string]interface{}{
141+
"spec": map[string]interface{}{"fieldA": "a"},
142+
"metadata": map[string]interface{}{"managedFields": []interface{}{"a", "b"}},
143+
}},
144+
expectSpecChanged: false,
145+
},
122146
}
123147
for _, scenario := range scenarios {
124148
t.Run(scenario.name, func(tt *testing.T) {
125-
specChanged, err := ensureRemaining(scenario.cacheObject, scenario.localObject)
149+
specChanged, _, err := ensureRemaining(scenario.cacheObject, scenario.localObject)
126150
if specChanged != scenario.expectSpecChanged {
127151
tt.Fatalf("spec changed = %v, expected spec to be changed = %v", specChanged, scenario.expectSpecChanged)
128152
}
@@ -232,7 +256,7 @@ func TestEnsureUnstructuredStatus(t *testing.T) {
232256
}
233257
for _, scenario := range scenarios {
234258
t.Run(scenario.name, func(tt *testing.T) {
235-
statusChanged, err := ensureRemaining(scenario.cacheObject, scenario.localObject)
259+
statusChanged, _, err := ensureRemaining(scenario.cacheObject, scenario.localObject)
236260
if statusChanged != scenario.expectStatusChanged {
237261
tt.Fatalf("status changed = %v, expected spec to be changed = %v", statusChanged, scenario.expectStatusChanged)
238262
}
@@ -392,7 +416,7 @@ func TestEnsureUnstructuredMeta(t *testing.T) {
392416
if err != nil {
393417
tt.Fatal(err)
394418
}
395-
metaChanged, err := ensureMeta(unstructuredCacheApiExport, unstructuredLocalApiExport)
419+
metaChanged, _, err := ensureMeta(unstructuredCacheApiExport, unstructuredLocalApiExport)
396420
if err != nil {
397421
tt.Fatal(err)
398422
}

0 commit comments

Comments
 (0)