Skip to content

Commit

Permalink
Delete the composition when synthesizer is missing (#247)
Browse files Browse the repository at this point in the history
Resolves #227 

Delete the composition when synthesizer is missing
  • Loading branch information
chihshenghuang authored Nov 15, 2024
1 parent 0f5de0a commit 1218f14
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 1 deletion.
9 changes: 8 additions & 1 deletion internal/controllers/synthesis/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,15 @@ func (c *podLifecycleController) reconcileDeletedComposition(ctx context.Context
comp.Status.CurrentSynthesis.ObservedCompositionGeneration = comp.Generation
comp.Status.CurrentSynthesis.Ready = nil
comp.Status.CurrentSynthesis.UUID = uuid.NewString()
comp.Status.CurrentSynthesis.Reconciled = nil
now := metav1.Now()
if (comp.Status.PreviousSynthesis == nil || comp.Status.PreviousSynthesis.Synthesized == nil) &&
comp.Status.CurrentSynthesis.Synthesized == nil {
// In this case, the composition is not reconciling due to the synthesizer is missing and the composition finalizer should be removed.
// If not, the composition will stuck in waiting for reconciliation to be completed and the it can't be deleted.
comp.Status.CurrentSynthesis.Reconciled = &now
} else {
comp.Status.CurrentSynthesis.Reconciled = nil
}
comp.Status.CurrentSynthesis.Synthesized = &now
err := c.client.Status().Update(ctx, comp)
if err != nil {
Expand Down
78 changes: 78 additions & 0 deletions internal/controllers/synthesis/lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,84 @@ func TestCompositionDeletion(t *testing.T) {
})
}

// TestDeleteCompositionWhenSynthesizerMissing proves that a composition's finalizer will be removed if the synthesizer is missing.
func TestDeleteCompositionWhenSynthesizerMissing(t *testing.T) {
ctx := testutil.NewContext(t)
mgr := testutil.NewManager(t)
cli := mgr.GetClient()

testutil.WithFakeExecutor(t, mgr, func(ctx context.Context, s *apiv1.Synthesizer, input *krmv1.ResourceList) (*krmv1.ResourceList, error) {
output := &krmv1.ResourceList{}
output.Items = []*unstructured.Unstructured{{
Object: map[string]any{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]string{
"name": "test",
"namespace": "default",
},
},
}}
return output, nil
})

require.NoError(t, NewPodLifecycleController(mgr.Manager, minimalTestConfig))
require.NoError(t, NewSliceCleanupController(mgr.Manager))
require.NoError(t, flowcontrol.NewSynthesisConcurrencyLimiter(mgr.Manager, 10, 0))
mgr.Start(t)

syn := &apiv1.Synthesizer{}
syn.Name = "test-syn-1"
syn.Spec.Image = "initial-image"
require.NoError(t, cli.Create(ctx, syn))

comp := &apiv1.Composition{}
comp.Name = "test-comp"
comp.Namespace = "default"
comp.Spec.Synthesizer.Name = syn.Name
require.NoError(t, cli.Create(ctx, comp))

// Create the composition's resource slice
testutil.Eventually(t, func() bool {
require.NoError(t, client.IgnoreNotFound(cli.Get(ctx, client.ObjectKeyFromObject(comp), comp)))
return comp.Status.CurrentSynthesis != nil && len(comp.Status.CurrentSynthesis.ResourceSlices) > 0
})
assert.NotNil(t, comp.Status.CurrentSynthesis.Initialized, "initialized timestamp is set")

// Wait for the resource slice to be created
testutil.Eventually(t, func() bool {
require.NoError(t, client.IgnoreNotFound(cli.Get(ctx, client.ObjectKeyFromObject(comp), comp)))
return comp.Status.CurrentSynthesis != nil && comp.Status.CurrentSynthesis.ResourceSlices != nil
})

// Delete the composition
require.NoError(t, cli.Delete(ctx, comp))
deleteGen := comp.Generation

// The generation should be updated
testutil.Eventually(t, func() bool {
require.NoError(t, client.IgnoreNotFound(cli.Get(ctx, client.ObjectKeyFromObject(comp), comp)))
return comp.Status.CurrentSynthesis != nil && comp.Status.CurrentSynthesis.ObservedCompositionGeneration >= deleteGen
})

// The composition should still exist after a bit
time.Sleep(time.Millisecond * 100)
require.NoError(t, cli.Get(ctx, client.ObjectKeyFromObject(comp), comp))

// Mark the synthesized time as nil to simulate the synthesizer is missing
err := retry.RetryOnConflict(testutil.Backoff, func() error {
cli.Get(ctx, client.ObjectKeyFromObject(comp), comp)
comp.Status.CurrentSynthesis.Synthesized = nil
return cli.Status().Update(ctx, comp)
})
require.NoError(t, err)

// The composition should eventually be released even the composition is waiting for reconciliation to be completed
testutil.Eventually(t, func() bool {
return errors.IsNotFound(cli.Get(ctx, client.ObjectKeyFromObject(comp), comp))
})
}

func TestNonExistentComposition(t *testing.T) {
ctx := testutil.NewContext(t)
mgr := testutil.NewManager(t)
Expand Down

0 comments on commit 1218f14

Please sign in to comment.