Skip to content

[bug]: Reconciliation 409-Conflict failing on Modified entities since v10 #1001

@joaope

Description

@joaope

Describe the bug

Reconciliation is failing on Modified events. No code changes since v9 apart from the updating of namespaces and returns due to breaking changes. Stack trace like so:

fail: KubeOps.Operator.Watcher.ResourceWatcher[0]
      Reconciliation of Modified for CustomObj/mycrd failed.
      k8s.Autorest.HttpOperationException: Operation returned an invalid status code 'Conflict', response body {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"Operation cannot be fulfilled on my.custom.objs.io \"mycrd\": the object has been modified; please apply your changes to the latest version and try again","reason":"Conflict","details":{"name":"mycrd","group":"my.custom.objs.io","kind":"mycdrs"},"code":409}
      
         at k8s.Kubernetes.SendRequestRaw(String requestContent, HttpRequestMessage httpRequest, CancellationToken cancellationToken)
         at k8s.AbstractKubernetes.ICustomObjectsOperations_ReplaceNamespacedCustomObjectWithHttpMessagesAsync[T](Object body, String group, String version, String namespaceParameter, String plural, String name, String dryRun, String fieldManager, String fieldValidation, IReadOnlyDictionary`2 customHeaders, CancellationToken cancellationToken)
         at k8s.AbstractKubernetes.k8s.ICustomObjectsOperations.ReplaceNamespacedCustomObjectWithHttpMessagesAsync[T](Object body, String group, String version, String namespaceParameter, String plural, String name, String dryRun, String fieldManager, String fieldValidation, IReadOnlyDictionary`2 customHeaders, CancellationToken cancellationToken)
         at k8s.GenericClient.ReplaceNamespacedAsync[T](T obj, String ns, String name, CancellationToken cancel)
         at KubeOps.KubernetesClient.KubernetesClient.UpdateAsync[TEntity](TEntity entity, CancellationToken cancellationToken)
         at KubeOps.Operator.Reconciliation.Reconciler`1.ReconcileEntity(TEntity entity, CancellationToken cancellationToken)
         at KubeOps.Operator.Reconciliation.Reconciler`1.ReconcileEntity(TEntity entity, CancellationToken cancellationToken)
         at KubeOps.Operator.Reconciliation.Reconciler`1.ReconcileModification(ReconciliationContext`1 reconciliationContext, CancellationToken cancellationToken)
         at KubeOps.Operator.Reconciliation.Reconciler`1.Reconcile(ReconciliationContext`1 reconciliationContext, CancellationToken cancellationToken)
         at KubeOps.Operator.Watcher.ResourceWatcher`1.OnEventAsync(WatchEventType eventType, TEntity entity, CancellationToken cancellationToken)
         at KubeOps.Operator.Watcher.ResourceWatcher`1.WatchClientEventsAsync(CancellationToken stoppingToken)

Worth adding that the problem doesn't seem to happen when turning AutoAttachFinalizers off so I belive the problem lies somewhere around this new Reconciler.cs, in particular this if-statement:

if (operatorSettings.AutoAttachFinalizers)
{
    var finalizers = scope.ServiceProvider.GetKeyedServices<IEntityFinalizer<TEntity>>(KeyedService.AnyKey);

    foreach (var finalizer in finalizers)
    {
        entity.AddFinalizer(finalizer.GetIdentifierName(entity));
    }

    entity = await client.UpdateAsync(entity, cancellationToken);
}

var controller = scope.ServiceProvider.GetRequiredService<IEntityController<TEntity>>();
return await controller.ReconcileAsync(entity, cancellationToken);

I don't have any finalizers but isn't this too late to update an entity with them? And also just before the reconciliation so any changes to the object will generate a 409.

This not only runs if I don't have finalizers but also every time an entity is Added/Modified as this logic weirdly threats both cases as the same when they have fundamental differences.

public async Task<ReconciliationResult<TEntity>> Reconcile(ReconciliationContext<TEntity> reconciliationContext, CancellationToken cancellationToken)
{
    var result = reconciliationContext.EventType switch
    {
        WatchEventType.Added or WatchEventType.Modified =>
            await ReconcileModification(reconciliationContext, cancellationToken),
        WatchEventType.Deleted =>
            await ReconcileDeletion(reconciliationContext, cancellationToken),
        _ => throw new NotSupportedException($"Reconciliation event type {reconciliationContext.EventType} is not supported!"),
    };
    
   // ...
}

These are some initial finds. I might be able to give some more context later. Thanks!

To reproduce

  1. Create controller that only changes entity status and updates it via client.UpdateStatusAsync()
  2. Add new entity to the cluster
  3. See reconciliation running
  4. Modify the object manually
  5. Fails with above exception

Expected behavior

Does not fail reconciliation

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions