Skip to content

Commit

Permalink
feat: add WithIgnoreTeardownWhile to qtransform controllers
Browse files Browse the repository at this point in the history
This is the opposite of `WithIgnoreTeardownUntil` - the runtime will not run teardown while the specified finalizers are still on the input resource. This further facilitates the ordering of "which controller runs its finalizerRemoval before/after which one".

Signed-off-by: Utku Ozdemir <[email protected]>
  • Loading branch information
utkuozdemir committed Nov 15, 2024
1 parent 5eca531 commit cf137ef
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 13 deletions.
34 changes: 25 additions & 9 deletions pkg/controller/generic/qtransform/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,13 @@ func mapperFuncFromGeneric[I generic.ResourceWithRD](generic MapperFuncGeneric[I

// ControllerOptions configures QTransformController.
type ControllerOptions struct {
mappers map[namespaceType]mapperFunc
leftoverFinalizers map[resource.Finalizer]struct{}
extraInputs []controller.Input
extraOutputs []controller.Output
primaryOutputKind controller.OutputKind
concurrency optional.Optional[uint]
mappers map[namespaceType]mapperFunc
ignoreTeardownUntilFinalizers map[resource.Finalizer]struct{}
ignoreTeardownWhileFinalizers map[resource.Finalizer]struct{}
extraInputs []controller.Input
extraOutputs []controller.Output
primaryOutputKind controller.OutputKind
concurrency optional.Optional[uint]
}

// ControllerOption is an option for QTransformController.
Expand Down Expand Up @@ -139,12 +140,27 @@ func WithConcurrency(n uint) ControllerOption {
// to be the last one not done with the resource.
func WithIgnoreTeardownUntil(finalizers ...resource.Finalizer) ControllerOption {
return func(o *ControllerOptions) {
if o.leftoverFinalizers == nil {
o.leftoverFinalizers = map[resource.Finalizer]struct{}{}
if o.ignoreTeardownUntilFinalizers == nil {
o.ignoreTeardownUntilFinalizers = map[resource.Finalizer]struct{}{}
}

for _, fin := range finalizers {
o.leftoverFinalizers[fin] = struct{}{}
o.ignoreTeardownUntilFinalizers[fin] = struct{}{}
}
}
}

// WithIgnoreTeardownWhile ignores input resource teardown while the input resource still has the mentioned finalizers.
//
// It is the opposite of WithIgnoreTeardownUntil.
func WithIgnoreTeardownWhile(finalizers ...resource.Finalizer) ControllerOption {
return func(o *ControllerOptions) {
if o.ignoreTeardownWhileFinalizers == nil {
o.ignoreTeardownWhileFinalizers = map[resource.Finalizer]struct{}{}
}

for _, fin := range finalizers {
o.ignoreTeardownWhileFinalizers[fin] = struct{}{}
}
}
}
10 changes: 6 additions & 4 deletions pkg/controller/generic/qtransform/qtransform.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,19 +200,21 @@ func (ctrl *QController[Input, Output]) Reconcile(ctx context.Context, logger *z

// if there's an option to ignore finalizers, check if we should ignore tearing down
// and perform "normal" reconcile instead
if ctrl.options.leftoverFinalizers != nil {
if ctrl.options.ignoreTeardownUntilFinalizers != nil || ctrl.options.ignoreTeardownWhileFinalizers != nil {
for _, fin := range *in.Metadata().Finalizers() {
if fin == ctrl.ControllerName {
continue
}

if _, present := ctrl.options.leftoverFinalizers[fin]; present {
if _, present = ctrl.options.ignoreTeardownUntilFinalizers[fin]; present {
continue
}

ignoreTearingDown = true
if _, present = ctrl.options.ignoreTeardownWhileFinalizers[fin]; present {
ignoreTearingDown = true

break
break
}
}
}

Expand Down
51 changes: 51 additions & 0 deletions pkg/controller/generic/qtransform/qtransform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ func TestDestroy(t *testing.T) {
})
}

//nolint:dupl
func TestDestroyWithIgnoreTeardownUntil(t *testing.T) {
setup(t, func(ctx context.Context, st state.State, runtime *runtime.Runtime) {
require.NoError(t, runtime.RegisterQController(NewABCController(qtransform.WithIgnoreTeardownUntil("extra-finalizer"))))
Expand Down Expand Up @@ -466,6 +467,56 @@ func TestDestroyWithIgnoreTeardownUntil(t *testing.T) {
})
}

//nolint:dupl
func TestDestroyWithIgnoreTeardownWhile(t *testing.T) {
setup(t, func(ctx context.Context, st state.State, runtime *runtime.Runtime) {
require.NoError(t, runtime.RegisterQController(NewABCController(qtransform.WithIgnoreTeardownWhile("extra-finalizer"))))

for _, a := range []*A{
NewA("1", ASpec{}),
NewA("2", ASpec{}),
NewA("3", ASpec{}),
} {
require.NoError(t, st.Create(ctx, a))
}

rtestutils.AssertResources(ctx, t, st, []resource.ID{"transformed-1", "transformed-2", "transformed-3"}, func(*B, *assert.Assertions) {})

// destroy without extra finalizers should work immediately
rtestutils.Destroy[*A](ctx, t, st, []resource.ID{"1"})
rtestutils.AssertNoResource[*B](ctx, t, st, "transformed-1")

// add two finalizers to '2'
require.NoError(t, st.AddFinalizer(ctx, NewA("2", ASpec{}).Metadata(), "extra-finalizer", "other-finalizer"))

// teardown input '2'
_, err := st.Teardown(ctx, NewA("2", ASpec{}).Metadata())
require.NoError(t, err)

// the output 'transformed-2' should not be torn down yet
rtestutils.AssertResources(ctx, t, st, []resource.ID{"transformed-2", "transformed-3"}, func(r *B, asrt *assert.Assertions) {
asrt.Equal(resource.PhaseRunning, r.Metadata().Phase())
})

// remove extra-finalizer
require.NoError(t, st.RemoveFinalizer(ctx, NewA("2", ASpec{}).Metadata(), "extra-finalizer"))
//
// the output 'transformed-2' should be destroyed now
rtestutils.AssertNoResource[*B](ctx, t, st, "transformed-2")
//
// the input '2' should no longer have controller finalizer
rtestutils.AssertResources(ctx, t, st, []resource.ID{"2"}, func(r *A, asrt *assert.Assertions) {
asrt.False(r.Metadata().Finalizers().Has("QTransformABCController"))
})

// remove other-finalizer
require.NoError(t, st.RemoveFinalizer(ctx, NewA("2", ASpec{}).Metadata(), "other-finalizer"))

// the input '2' should be destroyed now
rtestutils.Destroy[*A](ctx, t, st, []resource.ID{"2"})
})
}

func TestDestroyOutputFinalizers(t *testing.T) {
setup(t, func(ctx context.Context, st state.State, runtime *runtime.Runtime) {
require.NoError(t, runtime.RegisterQController(NewABNoFinalizerRemovalController()))
Expand Down

0 comments on commit cf137ef

Please sign in to comment.