Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions internal/sync/object_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ type objectSyncer struct {
// receive events. Since these objects might be created during the sync,
// they cannot be specified here directly.
eventObjSide syncSideType
// forceDelete triggers the deletion flow even when the source object is not
// being deleted; used to clean up related resources when the primary object
// is being deleted.
forceDelete bool
}

type syncSideType int
Expand Down Expand Up @@ -93,8 +97,8 @@ func (s *objectSyncer) recordEvent(ctx context.Context, source, dest syncSide, e
}

func (s *objectSyncer) Sync(ctx context.Context, log *zap.SugaredLogger, source, dest syncSide) (requeue bool, err error) {
// handle deletion: if source object is in deletion, delete the destination object (the clone)
if source.object.GetDeletionTimestamp() != nil {
// handle deletion: if source object is in deletion (or forced), delete the destination object (the clone)
if source.object.GetDeletionTimestamp() != nil || s.forceDelete {
return s.handleDeletion(ctx, log, source, dest)
}

Expand Down Expand Up @@ -458,16 +462,9 @@ func (s *objectSyncer) handleDeletion(ctx context.Context, log *zap.SugaredLogge
// if we just removed the finalizer, we can requeue the source object
if updated {
s.recordEvent(ctx, source, dest, corev1.EventTypeNormal, "ObjectDeleted", "Object deletion has been completed, finalizer has been removed.")
return true, nil
}

// For now we do not delete related resources; since after this step the destination object is
// gone already, the remaining syncer logic would fail if it attempts to sync relate objects.
// For the MVP it's fine to just leave related resources around, but in the future this behaviour
// might be configurable per PublishedResource, in which case this `return true` here would need
// to go away and the cleanup in general would need to be rethought a bit (maybe owner refs would
// be a good idea?).
return true, nil
return updated, nil
}

func (s *objectSyncer) removeSubresources(obj *unstructured.Unstructured) *unstructured.Unstructured {
Expand Down
23 changes: 21 additions & 2 deletions internal/sync/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,19 @@ func (s *ResourceSyncer) Process(ctx context.Context, remoteObj *unstructured.Un
// (i.e. on the service cluster), so that the original and copy are linked
// together and can be found.
metadataOnDestination: true,
eventObjSide: syncSideSource,
eventObjSide: syncSideSource,
}

// When the primary object is being deleted, clean up related resources FIRST,
// while the local object still exists (needed for reference resolution).
if remoteObj.GetDeletionTimestamp() != nil && destSide.object != nil {
relRequeue, relErr := s.processRelatedResources(ctx, log, stateStore, sourceSide, destSide, true)
if relErr != nil {
return false, fmt.Errorf("failed to clean up related resources during primary deletion: %w", relErr)
}
if relRequeue {
return true, nil
}
}

requeue, err = syncer.Sync(ctx, log, sourceSide, destSide)
Expand All @@ -215,7 +227,14 @@ func (s *ResourceSyncer) Process(ctx context.Context, remoteObj *unstructured.Un
// it modifies the state of the world, otherwise the objects in
// source/dest.object might be ouf date.

return s.processRelatedResources(ctx, log, stateStore, sourceSide, destSide)
// Guard: related resource resolution requires both sync sides to exist.
// destSide.object can be nil if the primary was in deletion and the local
// copy was already removed. In that case there is nothing left to sync.
if destSide.object == nil {
return false, nil
}

return s.processRelatedResources(ctx, log, stateStore, sourceSide, destSide, false)
}

func (s *ResourceSyncer) findLocalObject(ctx context.Context, objectKey objectKey) (*unstructured.Unstructured, error) {
Expand Down
9 changes: 6 additions & 3 deletions internal/sync/syncer_related.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ import (
ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
)

func (s *ResourceSyncer) processRelatedResources(ctx context.Context, log *zap.SugaredLogger, stateStore ObjectStateStore, remote, local syncSide) (requeue bool, err error) {
func (s *ResourceSyncer) processRelatedResources(ctx context.Context, log *zap.SugaredLogger, stateStore ObjectStateStore, remote, local syncSide, primaryDeleting bool) (requeue bool, err error) {
for _, relatedResource := range s.pubRes.Spec.Related {
requeue, err := s.processRelatedResource(ctx, log.With("identifier", relatedResource.Identifier), stateStore, remote, local, relatedResource)
requeue, err := s.processRelatedResource(ctx, log.With("identifier", relatedResource.Identifier), stateStore, remote, local, relatedResource, primaryDeleting)
if err != nil {
return false, fmt.Errorf("failed to process related resource %s: %w", relatedResource.Identifier, err)
}
Expand All @@ -62,7 +62,7 @@ type relatedObjectAnnotation struct {
Kind string `json:"kind"`
}

func (s *ResourceSyncer) processRelatedResource(ctx context.Context, log *zap.SugaredLogger, stateStore ObjectStateStore, remote, local syncSide, relRes syncagentv1alpha1.RelatedResourceSpec) (requeue bool, err error) {
func (s *ResourceSyncer) processRelatedResource(ctx context.Context, log *zap.SugaredLogger, stateStore ObjectStateStore, remote, local syncSide, relRes syncagentv1alpha1.RelatedResourceSpec, primaryDeleting bool) (requeue bool, err error) {
// decide what direction to sync (local->remote vs. remote->local)
var (
origin syncSide
Expand Down Expand Up @@ -163,6 +163,9 @@ func (s *ResourceSyncer) processRelatedResource(ctx context.Context, log *zap.Su
metadataOnDestination: false,
// events are always created on the kcp side
eventObjSide: eventObjSide,
// force deletion of related resources when the primary object is being deleted
// (only for origin:kcp resources that have blockSourceDeletion)
forceDelete: primaryDeleting && relRes.Origin == "kcp",
}

req, err := syncer.Sync(ctx, log, sourceSide, destSide)
Expand Down