@@ -204,27 +204,35 @@ func (svmc *SVMController) sync(ctx context.Context, key string) error {
204
204
}
205
205
gvr := getGVRFromResource (toBeProcessedSVM )
206
206
207
- resourceMonitor , err := svmc .dependencyGraphBuilder .GetMonitor (ctx , gvr )
207
+ // prevent unsynced monitor from blocking forever
208
+ // use a short timeout so that we can fail quickly and possibly handle other migrations while this monitor gets ready.
209
+ monCtx , monCtxCancel := context .WithTimeout (ctx , 10 * time .Second )
210
+ defer monCtxCancel ()
211
+ resourceMonitor , errMonitor := svmc .dependencyGraphBuilder .GetMonitor (monCtx , gvr )
208
212
if resourceMonitor != nil {
209
- if err != nil {
213
+ if errMonitor != nil {
210
214
// non nil monitor indicates that error is due to resource not being synced
211
- return fmt .Errorf ("dependency graph is not synced, requeuing to attempt again" )
215
+ return fmt .Errorf ("dependency graph is not synced, requeuing to attempt again: %w" , errMonitor )
212
216
}
213
217
} else {
218
+ logger .V (4 ).Error (errMonitor , "resource does not exist in GC" , "gvr" , gvr .String ())
219
+
220
+ // our GC cache could be missing a recently created custom resource, so give it some time to catch up
221
+ // we resync discovery every 30 seconds so twice that should be sufficient
222
+ if toBeProcessedSVM .CreationTimestamp .Add (time .Minute ).After (time .Now ()) {
223
+ return fmt .Errorf ("resource does not exist in GC, requeuing to attempt again: %w" , errMonitor )
224
+ }
225
+
214
226
// we can't migrate a resource that doesn't exist in the GC
215
- _ , err = svmc .kubeClient .StoragemigrationV1alpha1 ().
227
+ _ , errStatus : = svmc .kubeClient .StoragemigrationV1alpha1 ().
216
228
StorageVersionMigrations ().
217
229
UpdateStatus (
218
230
ctx ,
219
- setStatusConditions (toBeProcessedSVM , svmv1alpha1 .MigrationFailed , migrationFailedStatusReason ),
231
+ setStatusConditions (toBeProcessedSVM , svmv1alpha1 .MigrationFailed , migrationFailedStatusReason , "resource not found" ),
220
232
metav1.UpdateOptions {},
221
233
)
222
- if err != nil {
223
- return err
224
- }
225
- logger .V (4 ).Error (fmt .Errorf ("error migrating the resource" ), "resource does not exist in GC" , "gvr" , gvr .String ())
226
234
227
- return nil
235
+ return errStatus
228
236
}
229
237
230
238
gcListResourceVersion , err := convertResourceVersionToInt (resourceMonitor .Controller .LastSyncResourceVersion ())
@@ -244,7 +252,7 @@ func (svmc *SVMController) sync(ctx context.Context, key string) error {
244
252
StorageVersionMigrations ().
245
253
UpdateStatus (
246
254
ctx ,
247
- setStatusConditions (toBeProcessedSVM , svmv1alpha1 .MigrationRunning , migrationRunningStatusReason ),
255
+ setStatusConditions (toBeProcessedSVM , svmv1alpha1 .MigrationRunning , migrationRunningStatusReason , "" ),
248
256
metav1.UpdateOptions {},
249
257
)
250
258
if err != nil {
@@ -255,60 +263,72 @@ func (svmc *SVMController) sync(ctx context.Context, key string) error {
255
263
if err != nil {
256
264
return err
257
265
}
258
- typeMeta := metav1.TypeMeta {}
259
- typeMeta .APIVersion , typeMeta .Kind = gvk .ToAPIVersionAndKind ()
260
- data , err := json .Marshal (typeMeta )
261
- if err != nil {
262
- return err
263
- }
264
266
265
267
// ToDo: implement a mechanism to resume migration from the last migrated resource in case of a failure
266
268
// process storage migration
267
- for _ , gvrKey := range resourceMonitor .Store .ListKeys () {
268
- namespace , name , err := cache . SplitMetaNamespaceKey ( gvrKey )
269
+ for _ , obj := range resourceMonitor .Store .List () {
270
+ accessor , err := meta . Accessor ( obj )
269
271
if err != nil {
270
272
return err
271
273
}
272
274
273
- _ , err = svmc .dynamicClient .Resource (gvr ).
274
- Namespace (namespace ).
275
+ typeMeta := typeMetaUIDRV {}
276
+ typeMeta .APIVersion , typeMeta .Kind = gvk .ToAPIVersionAndKind ()
277
+ // set UID so that when a resource gets deleted, we get an "uid mismatch"
278
+ // conflict error instead of trying to create it.
279
+ typeMeta .UID = accessor .GetUID ()
280
+ // set RV so that when a resources gets updated or deleted+recreated, we get an "object has been modified"
281
+ // conflict error. we do not actually need to do anything special for the updated case because if RV
282
+ // was not set, it would just result in no-op request. but for the deleted+recreated case, if RV is
283
+ // not set but UID is set, we would get an immutable field validation error. hence we must set both.
284
+ typeMeta .ResourceVersion = accessor .GetResourceVersion ()
285
+ data , err := json .Marshal (typeMeta )
286
+ if err != nil {
287
+ return err
288
+ }
289
+
290
+ _ , errPatch := svmc .dynamicClient .Resource (gvr ).
291
+ Namespace (accessor .GetNamespace ()).
275
292
Patch (ctx ,
276
- name ,
293
+ accessor . GetName () ,
277
294
types .ApplyPatchType ,
278
295
data ,
279
296
metav1.PatchOptions {
280
297
FieldManager : svmc .controllerName ,
281
298
},
282
299
)
283
- if err != nil {
284
- // in case of NotFound or Conflict, we can stop processing migration for that resource
285
- if apierrors .IsNotFound (err ) || apierrors .IsConflict (err ) {
286
- continue
287
- }
288
300
289
- _ , err = svmc .kubeClient .StoragemigrationV1alpha1 ().
301
+ // in case of conflict, we can stop processing migration for that resource because it has either been
302
+ // - updated, meaning that migration has already been performed
303
+ // - deleted, meaning that migration is not needed
304
+ // - deleted and recreated, meaning that migration has already been performed
305
+ if apierrors .IsConflict (errPatch ) {
306
+ logger .V (6 ).Info ("Resource ignored due to conflict" , "namespace" , accessor .GetNamespace (), "name" , accessor .GetName (), "gvr" , gvr .String (), "err" , errPatch )
307
+ continue
308
+ }
309
+
310
+ if errPatch != nil {
311
+ logger .V (4 ).Error (errPatch , "Failed to migrate the resource" , "namespace" , accessor .GetNamespace (), "name" , accessor .GetName (), "gvr" , gvr .String (), "reason" , apierrors .ReasonForError (errPatch ))
312
+
313
+ _ , errStatus := svmc .kubeClient .StoragemigrationV1alpha1 ().
290
314
StorageVersionMigrations ().
291
315
UpdateStatus (
292
316
ctx ,
293
- setStatusConditions (toBeProcessedSVM , svmv1alpha1 .MigrationFailed , migrationFailedStatusReason ),
317
+ setStatusConditions (toBeProcessedSVM , svmv1alpha1 .MigrationFailed , migrationFailedStatusReason , "migration encountered unhandled error" ),
294
318
metav1.UpdateOptions {},
295
319
)
296
- if err != nil {
297
- return err
298
- }
299
- logger .V (4 ).Error (err , "Failed to migrate the resource" , "name" , gvrKey , "gvr" , gvr .String (), "reason" , apierrors .ReasonForError (err ))
300
320
301
- return nil
321
+ return errStatus
302
322
// Todo: add retry for scenarios where API server returns rate limiting error
303
323
}
304
- logger .V (4 ).Info ("Successfully migrated the resource" , "name" , gvrKey , "gvr" , gvr .String ())
324
+ logger .V (4 ).Info ("Successfully migrated the resource" , "namespace" , accessor . GetNamespace (), " name" , accessor . GetName () , "gvr" , gvr .String ())
305
325
}
306
326
307
327
_ , err = svmc .kubeClient .StoragemigrationV1alpha1 ().
308
328
StorageVersionMigrations ().
309
329
UpdateStatus (
310
330
ctx ,
311
- setStatusConditions (toBeProcessedSVM , svmv1alpha1 .MigrationSucceeded , migrationSuccessStatusReason ),
331
+ setStatusConditions (toBeProcessedSVM , svmv1alpha1 .MigrationSucceeded , migrationSuccessStatusReason , "" ),
312
332
metav1.UpdateOptions {},
313
333
)
314
334
if err != nil {
@@ -318,3 +338,13 @@ func (svmc *SVMController) sync(ctx context.Context, key string) error {
318
338
logger .V (4 ).Info ("Finished syncing svm resource" , "key" , key , "gvr" , gvr .String (), "elapsed" , time .Since (startTime ))
319
339
return nil
320
340
}
341
+
342
+ type typeMetaUIDRV struct {
343
+ metav1.TypeMeta `json:",inline"`
344
+ objectMetaUIDandRV `json:"metadata,omitempty"`
345
+ }
346
+
347
+ type objectMetaUIDandRV struct {
348
+ UID types.UID `json:"uid,omitempty"`
349
+ ResourceVersion string `json:"resourceVersion,omitempty"`
350
+ }
0 commit comments