Skip to content

Commit

Permalink
Prune/Delete should use the specified propagation policy
Browse files Browse the repository at this point in the history
  • Loading branch information
mortent committed Aug 17, 2021
1 parent b146dfa commit 91df668
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 47 deletions.
2 changes: 1 addition & 1 deletion pkg/apply/applier.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func setDefaults(o *Options) {
if o.PollInterval == time.Duration(0) {
o.PollInterval = poller.DefaultPollInterval
}
if o.PrunePropagationPolicy == metav1.DeletionPropagation("") {
if o.PrunePropagationPolicy == "" {
o.PrunePropagationPolicy = metav1.DeletePropagationBackground
}
}
Expand Down
16 changes: 15 additions & 1 deletion pkg/apply/destroyer.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,27 @@ type DestroyerOptions struct {
// EmitStatusEvents defines whether status events should be
// emitted on the eventChannel to the caller.
EmitStatusEvents bool

// PollInterval defines how often we should poll for the status
// of resources.
PollInterval time.Duration
}

func setDestroyerDefaults(o *DestroyerOptions) {
if o.PollInterval == time.Duration(0) {
o.PollInterval = poller.DefaultPollInterval
}
if o.DeletePropagationPolicy == "" {
o.DeletePropagationPolicy = metav1.DeletePropagationBackground
}
}

// Run performs the destroy step. Passes the inventory object. This
// happens asynchronously on progress and any errors are reported
// back on the event channel.
func (d *Destroyer) Run(inv inventory.InventoryInfo, options DestroyerOptions) <-chan event.Event {
eventChannel := make(chan event.Event)
setDestroyerDefaults(&options)
go func() {
defer close(eventChannel)
// Retrieve the objects to be deleted from the cluster. Second parameter is empty
Expand Down Expand Up @@ -137,7 +151,7 @@ func (d *Destroyer) Run(inv inventory.InventoryInfo, options DestroyerOptions) <
// TODO(seans): Make the poll interval configurable like the applier.
err = runner.Run(context.Background(), taskQueue.ToChannel(), eventChannel, taskrunner.Options{
UseCache: true,
PollInterval: poller.DefaultPollInterval,
PollInterval: options.PollInterval,
EmitStatusEvents: options.EmitStatusEvents,
})
if err != nil {
Expand Down
4 changes: 3 additions & 1 deletion pkg/apply/prune/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ func (po *PruneOptions) Prune(pruneObjs []*unstructured.Unstructured,
taskContext.CapturePruneFailure(pruneID)
continue
}
err = namespacedClient.Delete(context.TODO(), pruneID.Name, metav1.DeleteOptions{})
err = namespacedClient.Delete(context.TODO(), pruneID.Name, metav1.DeleteOptions{
PropagationPolicy: &o.PropagationPolicy,
})
if err != nil {
if klog.V(4).Enabled() {
klog.Errorf("prune failed for %s (%s)", pruneID, err)
Expand Down
121 changes: 77 additions & 44 deletions pkg/apply/prune/prune_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ var testNamespace = "test-inventory-namespace"
var inventoryObjName = "test-inventory-obj"
var podName = "pod-1"
var pdbName = "pdb"
var roleName = "role"

var testInventoryLabel = "test-app-label"

Expand Down Expand Up @@ -110,21 +109,6 @@ var pdbDeleteFailure = &unstructured.Unstructured{
},
}

var role = &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": map[string]interface{}{
"name": roleName,
"namespace": testNamespace,
"uid": "uid3",
"annotations": map[string]interface{}{
"config.k8s.io/owning-inventory": testInventoryLabel,
},
},
},
}

// Returns a inventory object with the inventory set from
// the passed "children".
func createInventoryInfo(children ...*unstructured.Unstructured) inventory.InventoryInfo {
Expand Down Expand Up @@ -432,6 +416,27 @@ func TestPrune(t *testing.T) {
}
}

// failureNamespaceClient wrappers around a namespaceClient with the overwriting to Get and Delete functions.
type failureNamespaceClient struct {
dynamic.ResourceInterface
}

var _ dynamic.ResourceInterface = &failureNamespaceClient{}

func (c *failureNamespaceClient) Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error {
if strings.Contains(name, "delete-failure") {
return fmt.Errorf("expected delete error")
}
return nil
}

func (c *failureNamespaceClient) Get(ctx context.Context, name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
if strings.Contains(name, "get-failure") {
return nil, fmt.Errorf("expected get error")
}
return pdb, nil
}

func TestPruneWithErrors(t *testing.T) {
tests := map[string]struct {
pruneObjs []*unstructured.Unstructured
Expand Down Expand Up @@ -471,8 +476,9 @@ func TestPruneWithErrors(t *testing.T) {
po := PruneOptions{
InvClient: inventory.NewFakeInventoryClient(pruneIds),
// Set up the fake dynamic client to recognize all objects, and the RESTMapper.
Client: &fakeDynamicFailureClient{dynamic: fake.NewSimpleDynamicClient(scheme.Scheme,
namespace, pdb, role)},
Client: &fakeDynamicClient{
resourceInterface: &failureNamespaceClient{},
},
Mapper: testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme,
scheme.Scheme.PrioritizedVersionsAllGroups()...),
}
Expand Down Expand Up @@ -579,44 +585,71 @@ func TestGetPruneObjs(t *testing.T) {
}
}

type fakeDynamicFailureClient struct {
dynamic dynamic.Interface
type optionsCaptureNamespaceClient struct {
dynamic.ResourceInterface
options metav1.DeleteOptions
}

var _ dynamic.Interface = &fakeDynamicFailureClient{}
var _ dynamic.ResourceInterface = &optionsCaptureNamespaceClient{}

func (c *fakeDynamicFailureClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
if resource.Resource == "poddisruptionbudgets" {
return &fakeDynamicResourceClient{NamespaceableResourceInterface: c.dynamic.Resource(resource)}
}
return c.dynamic.Resource(resource)
func (c *optionsCaptureNamespaceClient) Delete(_ context.Context, _ string, options metav1.DeleteOptions, _ ...string) error {
c.options = options
return nil
}

type fakeDynamicResourceClient struct {
dynamic.NamespaceableResourceInterface
}
func TestPrune_PropagationPolicy(t *testing.T) {
testCases := map[string]struct {
propagationPolicy metav1.DeletionPropagation
}{
"background propagation policy": {
propagationPolicy: metav1.DeletePropagationBackground,
},
"foreground propagation policy": {
propagationPolicy: metav1.DeletePropagationForeground,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
captureClient := &optionsCaptureNamespaceClient{}
po := PruneOptions{
InvClient: inventory.NewFakeInventoryClient([]object.ObjMetadata{}),
Client: &fakeDynamicClient{
resourceInterface: captureClient,
},
Mapper: testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme,
scheme.Scheme.PrioritizedVersionsAllGroups()...),
}

func (c *fakeDynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
return &fakeNamespaceClient{ResourceInterface: c.NamespaceableResourceInterface.Namespace(ns)}
eventChannel := make(chan event.Event, 1)
taskContext := taskrunner.NewTaskContext(eventChannel)
err := po.Prune([]*unstructured.Unstructured{pdb}, []filter.ValidationFilter{}, taskContext, Options{
PropagationPolicy: tc.propagationPolicy,
})
assert.NoError(t, err)
require.NotNil(t, captureClient.options.PropagationPolicy)
assert.Equal(t, tc.propagationPolicy, *captureClient.options.PropagationPolicy)
})
}
}

// fakeNamespaceClient wrappers around a namespaceClient with the overwriting to Get and Delete functions.
type fakeNamespaceClient struct {
dynamic.ResourceInterface
type fakeDynamicClient struct {
resourceInterface dynamic.ResourceInterface
}

var _ dynamic.ResourceInterface = &fakeNamespaceClient{}
var _ dynamic.Interface = &fakeDynamicClient{}

func (c *fakeNamespaceClient) Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error {
if strings.Contains(name, "delete-failure") {
return fmt.Errorf("expected delete error")
func (c *fakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
return &fakeDynamicResourceClient{
resourceInterface: c.resourceInterface,
NamespaceableResourceInterface: fake.NewSimpleDynamicClient(scheme.Scheme).Resource(resource),
}
return nil
}

func (c *fakeNamespaceClient) Get(ctx context.Context, name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
if strings.Contains(name, "get-failure") {
return nil, fmt.Errorf("expected get error")
}
return pdb, nil
type fakeDynamicResourceClient struct {
dynamic.NamespaceableResourceInterface
resourceInterface dynamic.ResourceInterface
}

func (c *fakeDynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
return c.resourceInterface
}

0 comments on commit 91df668

Please sign in to comment.