@@ -3,12 +3,12 @@ package client
33import (
44 "context"
55 "fmt"
6+ "k8s.io/apimachinery/pkg/api/meta"
67 "net/http"
78 "strconv"
89 "sync"
910
1011 "k8s.io/apimachinery/pkg/api/errors"
11- "k8s.io/apimachinery/pkg/api/meta"
1212 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1313 kubetypes "k8s.io/apimachinery/pkg/types"
1414 "k8s.io/apimachinery/pkg/watch"
@@ -41,7 +41,7 @@ type ManifestWorkAgentClient struct {
4141var _ workv1client.ManifestWorkInterface = & ManifestWorkAgentClient {}
4242
4343func NewManifestWorkAgentClient (
44- clusterName string ,
44+ _ string ,
4545 watcherStore store.ClientWatcherStore [* workv1.ManifestWork ],
4646 cloudEventsClient generic.CloudEventsClient [* workv1.ManifestWork ],
4747) * ManifestWorkAgentClient {
@@ -126,22 +126,39 @@ func (c *ManifestWorkAgentClient) Watch(ctx context.Context, opts metav1.ListOpt
126126
127127func (c * ManifestWorkAgentClient ) Patch (ctx context.Context , name string , pt kubetypes.PatchType , data []byte , opts metav1.PatchOptions , subresources ... string ) (result * workv1.ManifestWork , err error ) {
128128 klog .V (4 ).Infof ("patching manifestwork %s/%s" , c .namespace , name )
129+
130+ // avoid race conditions among the agent's go routines
131+ c .Lock ()
132+ defer c .Unlock ()
133+
134+ var returnErr * errors.StatusError
135+ defer func () {
136+ if returnErr != nil {
137+ metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
138+ } else {
139+ metrics .IncreaseWorkProcessedCounter ("patch" , metav1 .StatusSuccess )
140+ }
141+ }()
142+
143+ if len (subresources ) != 0 && ! utils .IsStatusPatch (subresources ) {
144+ msg := fmt .Sprintf ("unsupported subresources %v" , subresources )
145+ returnErr = errors .NewGenericServerResponse (http .StatusMethodNotAllowed , "patch" , common .ManifestWorkGR , name , msg , 0 , false )
146+ return nil , returnErr
147+ }
148+
129149 lastWork , exists , err := c .watcherStore .Get (c .namespace , name )
130150 if err != nil {
131- returnErr := errors .NewInternalError (err )
132- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
151+ returnErr = errors .NewInternalError (err )
133152 return nil , returnErr
134153 }
135154 if ! exists {
136- returnErr := errors .NewNotFound (common .ManifestWorkGR , name )
137- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
155+ returnErr = errors .NewNotFound (common .ManifestWorkGR , name )
138156 return nil , returnErr
139157 }
140158
141159 patchedWork , err := utils .Patch (pt , lastWork , data )
142160 if err != nil {
143- returnErr := errors .NewInternalError (err )
144- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
161+ returnErr = errors .NewInternalError (err )
145162 return nil , returnErr
146163 }
147164
@@ -155,93 +172,43 @@ func (c *ManifestWorkAgentClient) Patch(ctx context.Context, name string, pt kub
155172 eventType := types.CloudEventsType {
156173 CloudEventsDataType : * eventDataType ,
157174 SubResource : types .SubResourceStatus ,
175+ Action : types .UpdateRequestAction ,
176+ }
177+
178+ // we first compare local resource version
179+ if returnErr = versoinCompare (patchedWork , lastWork ); returnErr != nil {
180+ return nil , returnErr
158181 }
159182
160183 newWork := patchedWork .DeepCopy ()
161184
162- if utils .IsStatusPatch (subresources ) {
163- // avoid race conditions among the agent's go routines
164- c .Lock ()
165- defer c .Unlock ()
185+ isDeleted := ! newWork .DeletionTimestamp .IsZero () && len (newWork .Finalizers ) == 0
166186
167- eventType .Action = types .UpdateRequestAction
168- // publish the status update event to source, source will check the resource version
169- // and reject the update if it's status update is outdated.
170- if err := c .cloudEventsClient .Publish (ctx , eventType , newWork ); err != nil {
171- returnErr := cloudeventserrors .ToStatusError (common .ManifestWorkGR , name , err )
172- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
173- return nil , returnErr
187+ if utils .IsStatusPatch (subresources ) || isDeleted {
188+ if isDeleted {
189+ meta .SetStatusCondition (& newWork .Status .Conditions , metav1.Condition {
190+ Type : common .ResourceDeleted ,
191+ Status : metav1 .ConditionTrue ,
192+ Reason : "ManifestsDeleted" ,
193+ Message : fmt .Sprintf ("The manifests are deleted from the cluster %s" , newWork .Namespace ),
194+ })
174195 }
175196
176- // Fetch the latest work from the store and verify the resource version to avoid updating the store
177- // with outdated work. Return a conflict error if the resource version is outdated.
178- // Due to the lack of read-modify-write guarantees in the store, race conditions may occur between
179- // this update operation and one from the agent informer after receiving the event from the source.
180- latestWork , exists , err := c .watcherStore .Get (c .namespace , name )
181- if err != nil {
182- returnErr := errors .NewInternalError (err )
183- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
184- return nil , returnErr
185- }
186- if ! exists {
187- returnErr := errors .NewNotFound (common .ManifestWorkGR , name )
188- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
189- return nil , returnErr
190- }
191- lastResourceVersion , err := strconv .ParseInt (latestWork .GetResourceVersion (), 10 , 64 )
192- if err != nil {
193- returnErr := errors .NewInternalError (err )
194- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
195- return nil , returnErr
196- }
197- newResourceVersion , err := strconv .ParseInt (newWork .GetResourceVersion (), 10 , 64 )
198- if err != nil {
199- returnErr := errors .NewInternalError (err )
200- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
201- return nil , returnErr
202- }
203- // ensure the resource version of the work is not outdated
204- if newResourceVersion < lastResourceVersion {
205- // It's safe to return a conflict error here, even if the status update event
206- // has already been sent. The source may reject the update due to an outdated resource version.
207- returnErr := errors .NewConflict (common .ManifestWorkGR , name , fmt .Errorf ("the resource version of the work is outdated" ))
208- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
209- return nil , returnErr
210- }
211- if err := c .watcherStore .Update (newWork ); err != nil {
212- returnErr := errors .NewInternalError (err )
213- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
197+ // Set work's resource version to remote resource version for publishing
198+ workToPublish := newWork .DeepCopy ()
199+ workToPublish .ResourceVersion = ""
200+
201+ // publish the status update event to source, source will check the resource version
202+ // and reject the update if it's status update is outdated.
203+ if err := c .cloudEventsClient .Publish (ctx , eventType , workToPublish ); err != nil {
204+ returnErr = cloudeventserrors .ToStatusError (common .ManifestWorkGR , name , err )
214205 return nil , returnErr
215206 }
216-
217- metrics .IncreaseWorkProcessedCounter ("patch" , metav1 .StatusSuccess )
218- return newWork , nil
219- }
220-
221- if len (subresources ) != 0 {
222- msg := fmt .Sprintf ("unsupported subresources %v" , subresources )
223- returnErr := errors .NewGenericServerResponse (http .StatusMethodNotAllowed , "patch" , common .ManifestWorkGR , name , msg , 0 , false )
224- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
225- return nil , returnErr
226207 }
227208
228209 // the finalizers of a deleting manifestwork are removed, marking the manifestwork status to deleted and sending
229210 // it back to source
230- if ! newWork .DeletionTimestamp .IsZero () && len (newWork .Finalizers ) == 0 {
231- meta .SetStatusCondition (& newWork .Status .Conditions , metav1.Condition {
232- Type : common .ResourceDeleted ,
233- Status : metav1 .ConditionTrue ,
234- Reason : "ManifestsDeleted" ,
235- Message : fmt .Sprintf ("The manifests are deleted from the cluster %s" , newWork .Namespace ),
236- })
237-
238- eventType .Action = types .UpdateRequestAction
239- if err := c .cloudEventsClient .Publish (ctx , eventType , newWork ); err != nil {
240- returnErr := cloudeventserrors .ToStatusError (common .ManifestWorkGR , name , err )
241- metrics .IncreaseWorkProcessedCounter ("delete" , string (returnErr .ErrStatus .Reason ))
242- return nil , returnErr
243- }
244-
211+ if isDeleted {
245212 if err := c .watcherStore .Delete (newWork ); err != nil {
246213 returnErr := errors .NewInternalError (err )
247214 metrics .IncreaseWorkProcessedCounter ("delete" , string (returnErr .ErrStatus .Reason ))
@@ -252,12 +219,49 @@ func (c *ManifestWorkAgentClient) Patch(ctx context.Context, name string, pt kub
252219 return newWork , nil
253220 }
254221
222+ // Fetch the latest work from the store and verify the resource version to avoid updating the store
223+ // with outdated work. Return a conflict error if the resource version is outdated.
224+ // Due to the lack of read-modify-write guarantees in the store, race conditions may occur between
225+ // this update operation and one from the agent informer after receiving the event from the source.
226+ latestWork , exists , err := c .watcherStore .Get (c .namespace , name )
227+ if err != nil {
228+ returnErr = errors .NewInternalError (err )
229+ return nil , returnErr
230+ }
231+ if ! exists {
232+ returnErr = errors .NewNotFound (common .ManifestWorkGR , name )
233+ return nil , returnErr
234+ }
235+ if returnErr = versoinCompare (patchedWork , latestWork ); returnErr != nil {
236+ return nil , returnErr
237+ }
255238 if err := c .watcherStore .Update (newWork ); err != nil {
256- returnErr := errors .NewInternalError (err )
257- metrics .IncreaseWorkProcessedCounter ("patch" , string (returnErr .ErrStatus .Reason ))
239+ returnErr = errors .NewInternalError (err )
258240 return nil , returnErr
259241 }
260-
261- metrics .IncreaseWorkProcessedCounter ("patch" , metav1 .StatusSuccess )
262242 return newWork , nil
263243}
244+
245+ func versoinCompare (new , old * workv1.ManifestWork ) * errors.StatusError {
246+ // If resource version is empty or 0, skip the comparison (e.g., when publishing status updates)
247+ if new .GetResourceVersion () == "" || new .GetResourceVersion () == "0" {
248+ return nil
249+ }
250+
251+ lastResourceVersion , err := strconv .ParseInt (old .GetResourceVersion (), 10 , 64 )
252+ if err != nil {
253+ return errors .NewInternalError (err )
254+ }
255+ newResourceVersion , err := strconv .ParseInt (new .GetResourceVersion (), 10 , 64 )
256+ if err != nil {
257+ return errors .NewInternalError (err )
258+ }
259+ // ensure the resource version of the work is not outdated
260+ if newResourceVersion < lastResourceVersion {
261+ // It's safe to return a conflict error here, even if the status update event
262+ // has already been sent. The source may reject the update due to an outdated resource version.
263+ return errors .NewConflict (common .ManifestWorkGR , new .Name , fmt .Errorf (
264+ "the resource version of the work is outdated, new %d, old %d" , newResourceVersion , lastResourceVersion ))
265+ }
266+ return nil
267+ }
0 commit comments