@@ -17,19 +17,48 @@ limitations under the License.
1717package ssa
1818
1919import (
20+ "context"
21+ "sync"
2022 "testing"
2123 "time"
2224
2325 . "github.com/onsi/gomega"
2426 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2527 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28+ "k8s.io/apimachinery/pkg/runtime"
2629 "k8s.io/utils/ptr"
2730 "sigs.k8s.io/controller-runtime/pkg/client"
2831
2932 clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
3033 "sigs.k8s.io/cluster-api/util/test/builder"
3134)
3235
36+ // applyCountingClient wraps a client and counts Apply calls.
37+ type applyCountingClient struct {
38+ client.Client
39+ applyCount int
40+ mu sync.Mutex
41+ }
42+
43+ func (c * applyCountingClient ) Apply (ctx context.Context , obj runtime.ApplyConfiguration , opts ... client.ApplyOption ) error {
44+ c .mu .Lock ()
45+ c .applyCount ++
46+ c .mu .Unlock ()
47+ return c .Client .Apply (ctx , obj , opts ... )
48+ }
49+
50+ func (c * applyCountingClient ) getCount () int {
51+ c .mu .Lock ()
52+ defer c .mu .Unlock ()
53+ return c .applyCount
54+ }
55+
56+ func (c * applyCountingClient ) resetCount () {
57+ c .mu .Lock ()
58+ c .applyCount = 0
59+ c .mu .Unlock ()
60+ }
61+
3362func TestPatch (t * testing.T ) {
3463 g := NewWithT (t )
3564
@@ -48,26 +77,43 @@ func TestPatch(t *testing.T) {
4877 fieldManager := "test-manager"
4978 ssaCache := NewCache ("test-controller" )
5079
80+ // Wrap the client to count API calls
81+ countingClient := & applyCountingClient {Client : env .GetClient ()}
82+
5183 // 1. Create the object
5284 createObject := initialObject .DeepCopy ()
53- g .Expect (Patch (ctx , env .GetClient (), fieldManager , createObject )).To (Succeed ())
85+ g .Expect (Patch (ctx , countingClient , fieldManager , createObject )).To (Succeed ())
86+ g .Expect (countingClient .getCount ()).To (Equal (1 ), "Expected 1 API call for create" )
5487
55- // 2. Update the object and verify that the request was not cached as the object was changed.
88+ // 2. Update the object and verify that the request was not cached with the old identifier,
89+ // but is cached with a new identifier (after apply).
5690 // Get the original object.
5791 originalObject := initialObject .DeepCopy ()
5892 g .Expect (env .GetAPIReader ().Get (ctx , client .ObjectKeyFromObject (originalObject ), originalObject )).To (Succeed ())
5993 // Modify the object
6094 modifiedObject := initialObject .DeepCopy ()
6195 g .Expect (unstructured .SetNestedField (modifiedObject .Object , "baz" , "spec" , "foo" )).To (Succeed ())
62- // Compute request identifier, so we can later verify that the update call was not cached.
96+ // Compute request identifier before the update , so we can later verify that the update call was not cached with this identifier .
6397 modifiedUnstructured , err := prepareModified (env .Scheme (), modifiedObject )
6498 g .Expect (err ).ToNot (HaveOccurred ())
65- requestIdentifier , err := ComputeRequestIdentifier (env .GetScheme (), originalObject , modifiedUnstructured )
99+ oldRequestIdentifier , err := ComputeRequestIdentifier (env .GetScheme (), originalObject , modifiedUnstructured )
66100 g .Expect (err ).ToNot (HaveOccurred ())
101+ // Save a copy of modifiedUnstructured before apply to compute the new identifier later
102+ modifiedUnstructuredBeforeApply := modifiedUnstructured .DeepCopy ()
67103 // Update the object
68- g .Expect (Patch (ctx , env .GetClient (), fieldManager , modifiedObject , WithCachingProxy {Cache : ssaCache , Original : originalObject })).To (Succeed ())
69- // Verify that request was not cached (as it changed the object)
70- g .Expect (ssaCache .Has (requestIdentifier , initialObject .GetKind ())).To (BeFalse ())
104+ countingClient .resetCount ()
105+ g .Expect (Patch (ctx , countingClient , fieldManager , modifiedObject , WithCachingProxy {Cache : ssaCache , Original : originalObject })).To (Succeed ())
106+ g .Expect (countingClient .getCount ()).To (Equal (1 ), "Expected 1 API call for first update (object changed)" )
107+ // Verify that request was not cached with the old identifier (as it changed the object)
108+ g .Expect (ssaCache .Has (oldRequestIdentifier , initialObject .GetKind ())).To (BeFalse ())
109+ // Get the actual object from server after apply to compute the new request identifier
110+ objectAfterApply := initialObject .DeepCopy ()
111+ g .Expect (env .GetAPIReader ().Get (ctx , client .ObjectKeyFromObject (objectAfterApply ), objectAfterApply )).To (Succeed ())
112+ // Compute the new request identifier (after apply)
113+ newRequestIdentifier , err := ComputeRequestIdentifier (env .GetScheme (), objectAfterApply , modifiedUnstructuredBeforeApply )
114+ g .Expect (err ).ToNot (HaveOccurred ())
115+ // Verify that request was cached with the new identifier (after apply)
116+ g .Expect (ssaCache .Has (newRequestIdentifier , initialObject .GetKind ())).To (BeTrue ())
71117
72118 // 3. Repeat the same update and verify that the request was cached as the object was not changed.
73119 // Get the original object.
@@ -79,12 +125,14 @@ func TestPatch(t *testing.T) {
79125 // Compute request identifier, so we can later verify that the update call was cached.
80126 modifiedUnstructured , err = prepareModified (env .Scheme (), modifiedObject )
81127 g .Expect (err ).ToNot (HaveOccurred ())
82- requestIdentifier , err = ComputeRequestIdentifier (env .GetScheme (), originalObject , modifiedUnstructured )
128+ requestIdentifierNoOp , err : = ComputeRequestIdentifier (env .GetScheme (), originalObject , modifiedUnstructured )
83129 g .Expect (err ).ToNot (HaveOccurred ())
84130 // Update the object
85- g .Expect (Patch (ctx , env .GetClient (), fieldManager , modifiedObject , WithCachingProxy {Cache : ssaCache , Original : originalObject })).To (Succeed ())
131+ countingClient .resetCount ()
132+ g .Expect (Patch (ctx , countingClient , fieldManager , modifiedObject , WithCachingProxy {Cache : ssaCache , Original : originalObject })).To (Succeed ())
133+ g .Expect (countingClient .getCount ()).To (Equal (0 ), "Expected 0 API calls for repeat update (should hit cache)" )
86134 // Verify that request was cached (as it did not change the object)
87- g .Expect (ssaCache .Has (requestIdentifier , initialObject .GetKind ())).To (BeTrue ())
135+ g .Expect (ssaCache .Has (requestIdentifierNoOp , initialObject .GetKind ())).To (BeTrue ())
88136 })
89137
90138 t .Run ("Test patch with Machine" , func (* testing.T ) {
@@ -123,30 +171,51 @@ func TestPatch(t *testing.T) {
123171 fieldManager := "test-manager"
124172 ssaCache := NewCache ("test-controller" )
125173
174+ // Wrap the client to count API calls
175+ countingClient := & applyCountingClient {Client : env .GetClient ()}
176+
126177 // 1. Create the object
127178 createObject := initialObject .DeepCopy ()
128- g .Expect (Patch (ctx , env .GetClient (), fieldManager , createObject )).To (Succeed ())
179+ g .Expect (Patch (ctx , countingClient , fieldManager , createObject )).To (Succeed ())
180+ g .Expect (countingClient .getCount ()).To (Equal (1 ), "Expected 1 API call for create" )
129181 // Verify that gvk is still set
130182 g .Expect (createObject .GroupVersionKind ()).To (Equal (initialObject .GroupVersionKind ()))
131183
132- // 2. Update the object and verify that the request was not cached as the object was changed.
184+ // 2. Update the object and verify that the request was not cached with the old identifier,
185+ // but is cached with a new identifier (after apply).
133186 // Get the original object.
134187 originalObject := initialObject .DeepCopy ()
135188 g .Expect (env .GetAPIReader ().Get (ctx , client .ObjectKeyFromObject (originalObject ), originalObject )).To (Succeed ())
136189 // Modify the object
137190 modifiedObject := initialObject .DeepCopy ()
138191 modifiedObject .Spec .Deletion .NodeDrainTimeoutSeconds = ptr .To (int32 (5 ))
139- // Compute request identifier, so we can later verify that the update call was not cached.
192+ // Compute request identifier before the update , so we can later verify that the update call was not cached with this identifier .
140193 modifiedUnstructured , err := prepareModified (env .Scheme (), modifiedObject )
141194 g .Expect (err ).ToNot (HaveOccurred ())
142- requestIdentifier , err := ComputeRequestIdentifier (env .GetScheme (), originalObject , modifiedUnstructured )
195+ oldRequestIdentifier , err := ComputeRequestIdentifier (env .GetScheme (), originalObject , modifiedUnstructured )
143196 g .Expect (err ).ToNot (HaveOccurred ())
197+ // Save a copy of modifiedUnstructured before apply to compute the new identifier later
198+ modifiedUnstructuredBeforeApply := modifiedUnstructured .DeepCopy ()
144199 // Update the object
145- g .Expect (Patch (ctx , env .GetClient (), fieldManager , modifiedObject , WithCachingProxy {Cache : ssaCache , Original : originalObject })).To (Succeed ())
200+ countingClient .resetCount ()
201+ g .Expect (Patch (ctx , countingClient , fieldManager , modifiedObject , WithCachingProxy {Cache : ssaCache , Original : originalObject })).To (Succeed ())
202+ g .Expect (countingClient .getCount ()).To (Equal (1 ), "Expected 1 API call for first update (object changed)" )
146203 // Verify that gvk is still set
147204 g .Expect (modifiedObject .GroupVersionKind ()).To (Equal (initialObject .GroupVersionKind ()))
148- // Verify that request was not cached (as it changed the object)
149- g .Expect (ssaCache .Has (requestIdentifier , initialObject .GetObjectKind ().GroupVersionKind ().Kind )).To (BeFalse ())
205+ // Verify that request was not cached with the old identifier (as it changed the object)
206+ g .Expect (ssaCache .Has (oldRequestIdentifier , initialObject .GetObjectKind ().GroupVersionKind ().Kind )).To (BeFalse ())
207+ // Get the actual object from server after apply to compute the new request identifier
208+ objectAfterApply := initialObject .DeepCopy ()
209+ g .Expect (env .GetAPIReader ().Get (ctx , client .ObjectKeyFromObject (objectAfterApply ), objectAfterApply )).To (Succeed ())
210+ // Convert to unstructured WITHOUT filtering to preserve server fields (like resourceVersion)
211+ objectAfterApplyUnstructured := & unstructured.Unstructured {}
212+ err = env .GetScheme ().Convert (objectAfterApply , objectAfterApplyUnstructured , nil )
213+ g .Expect (err ).ToNot (HaveOccurred ())
214+ // Compute the new request identifier (after apply)
215+ newRequestIdentifier , err := ComputeRequestIdentifier (env .GetScheme (), objectAfterApplyUnstructured , modifiedUnstructuredBeforeApply )
216+ g .Expect (err ).ToNot (HaveOccurred ())
217+ // Verify that request was cached with the new identifier (after apply)
218+ g .Expect (ssaCache .Has (newRequestIdentifier , initialObject .GetObjectKind ().GroupVersionKind ().Kind )).To (BeTrue ())
150219
151220 // Wait for 1 second. We are also trying to verify in this test that the resourceVersion of the Machine
152221 // is not increased. Under some circumstances this would only happen if the timestamp in managedFields would
@@ -166,12 +235,14 @@ func TestPatch(t *testing.T) {
166235 // Compute request identifier, so we can later verify that the update call was cached.
167236 modifiedUnstructured , err = prepareModified (env .Scheme (), modifiedObject )
168237 g .Expect (err ).ToNot (HaveOccurred ())
169- requestIdentifier , err = ComputeRequestIdentifier (env .GetScheme (), originalObject , modifiedUnstructured )
238+ requestIdentifierNoOp , err : = ComputeRequestIdentifier (env .GetScheme (), originalObject , modifiedUnstructured )
170239 g .Expect (err ).ToNot (HaveOccurred ())
171240 // Update the object
172- g .Expect (Patch (ctx , env .GetClient (), fieldManager , modifiedObject , WithCachingProxy {Cache : ssaCache , Original : originalObject })).To (Succeed ())
241+ countingClient .resetCount ()
242+ g .Expect (Patch (ctx , countingClient , fieldManager , modifiedObject , WithCachingProxy {Cache : ssaCache , Original : originalObject })).To (Succeed ())
243+ g .Expect (countingClient .getCount ()).To (Equal (0 ), "Expected 0 API calls for repeat update (should hit cache)" )
173244 // Verify that request was cached (as it did not change the object)
174- g .Expect (ssaCache .Has (requestIdentifier , initialObject .GetObjectKind ().GroupVersionKind ().Kind )).To (BeTrue ())
245+ g .Expect (ssaCache .Has (requestIdentifierNoOp , initialObject .GetObjectKind ().GroupVersionKind ().Kind )).To (BeTrue ())
175246 // Verify that gvk is still set
176247 g .Expect (modifiedObject .GroupVersionKind ()).To (Equal (initialObject .GroupVersionKind ()))
177248 })
0 commit comments