diff --git a/pkg/controller/chi/controller.go b/pkg/controller/chi/controller.go index 231cef3cc..e21bdfac2 100644 --- a/pkg/controller/chi/controller.go +++ b/pkg/controller/chi/controller.go @@ -153,6 +153,28 @@ func (c *Controller) createQueue() queue.PriorityQueue { //), } +func deletedObject[T any](obj interface{}) (*T, error) { + if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { + obj = tombstone.Obj + } + deleted, ok := obj.(*T) + if !ok || deleted == nil { + return nil, fmt.Errorf("unexpected object type %T", obj) + } + return deleted, nil +} + +func deleteHandler[T any](name string, handle func(*T)) func(interface{}) { + return func(obj interface{}) { + deleted, err := deletedObject[T](obj) + if err != nil { + utilRuntime.HandleError(fmt.Errorf("%s: %w", name, err)) + return + } + handle(deleted) + } +} + func (c *Controller) addEventHandlersCHI( chopInformerFactory chopInformers.SharedInformerFactory, ) { @@ -174,14 +196,13 @@ func (c *Controller) addEventHandlersCHI( log.V(3).M(newChi).Info("chiInformer.UpdateFunc") c.enqueueObject(cmd_queue.NewReconcileCHI(cmd_queue.ReconcileUpdate, oldChi, newChi)) }, - DeleteFunc: func(obj interface{}) { - chi := obj.(*api.ClickHouseInstallation) + DeleteFunc: deleteHandler("chiInformer.DeleteFunc", func(chi *api.ClickHouseInstallation) { if !chop.Config().IsNamespaceWatched(chi.Namespace) { return } log.V(3).M(chi).Info("chiInformer.DeleteFunc") c.enqueueObject(cmd_queue.NewReconcileCHI(cmd_queue.ReconcileDelete, chi, nil)) - }, + }), }) } @@ -207,14 +228,13 @@ func (c *Controller) addEventHandlersCHIT( log.V(3).M(newChit).Info("chitInformer.UpdateFunc") c.enqueueObject(cmd_queue.NewReconcileCHIT(cmd_queue.ReconcileUpdate, oldChit, newChit)) }, - DeleteFunc: func(obj interface{}) { - chit := obj.(*api.ClickHouseInstallationTemplate) + DeleteFunc: deleteHandler("chitInformer.DeleteFunc", func(chit *api.ClickHouseInstallationTemplate) { if !chop.Config().IsNamespaceWatched(chit.Namespace) { return } log.V(3).M(chit).Info("chitInformer.DeleteFunc") c.enqueueObject(cmd_queue.NewReconcileCHIT(cmd_queue.ReconcileDelete, chit, nil)) - }, + }), }) } @@ -233,11 +253,10 @@ func (c *Controller) addEventHandlersChopConfig( log.V(3).M(newChopConfig).Info("chopInformer.UpdateFunc") c.enqueueObject(cmd_queue.NewReconcileChopConfig(cmd_queue.ReconcileUpdate, oldChopConfig, newChopConfig)) }, - DeleteFunc: func(obj interface{}) { - chopConfig := obj.(*api.ClickHouseOperatorConfiguration) + DeleteFunc: deleteHandler("chopInformer.DeleteFunc", func(chopConfig *api.ClickHouseOperatorConfiguration) { log.V(3).M(chopConfig).Info("chopInformer.DeleteFunc") c.enqueueObject(cmd_queue.NewReconcileChopConfig(cmd_queue.ReconcileDelete, chopConfig, nil)) - }, + }), }) } @@ -259,13 +278,12 @@ func (c *Controller) addEventHandlersService( } log.V(3).M(oldService).Info("serviceInformer.UpdateFunc") }, - DeleteFunc: func(obj interface{}) { - service := obj.(*core.Service) + DeleteFunc: deleteHandler("serviceInformer.DeleteFunc", func(service *core.Service) { if !c.isTrackedObject(&service.ObjectMeta) { return } log.V(3).M(service).Info("serviceInformer.DeleteFunc") - }, + }), }) } @@ -394,13 +412,12 @@ func (c *Controller) addEventHandlersEndpoints( c.enqueueObject(cmd_queue.NewReconcileEndpoints(cmd_queue.ReconcileUpdate, oldEndpoints, newEndpoints)) } }, - DeleteFunc: func(obj interface{}) { - endpoints := obj.(*core.Endpoints) + DeleteFunc: deleteHandler("endpointsInformer.DeleteFunc", func(endpoints *core.Endpoints) { if !c.isTrackedObject(&endpoints.ObjectMeta) { return } log.V(3).M(endpoints).Info("endpointsInformer.DeleteFunc") - }, + }), }) } @@ -426,13 +443,12 @@ func (c *Controller) addEventHandlersEndpointSlice( c.enqueueObject(cmd_queue.NewReconcileEndpointSlice(cmd_queue.ReconcileUpdate, oldEndpointSlice, newEndpointSlice)) } }, - DeleteFunc: func(obj interface{}) { - endpointSlice := obj.(*discovery.EndpointSlice) + DeleteFunc: deleteHandler("endpointSliceInformer.DeleteFunc", func(endpointSlice *discovery.EndpointSlice) { if !c.isTrackedObject(&endpointSlice.ObjectMeta) { return } log.V(3).M(endpointSlice).Info("endpointSliceInformer.DeleteFunc") - }, + }), }) } @@ -454,13 +470,12 @@ func (c *Controller) addEventHandlersConfigMap( } log.V(3).M(configMap).Info("configMapInformer.UpdateFunc") }, - DeleteFunc: func(obj interface{}) { - configMap := obj.(*core.ConfigMap) + DeleteFunc: deleteHandler("configMapInformer.DeleteFunc", func(configMap *core.ConfigMap) { if !c.isTrackedObject(&configMap.ObjectMeta) { return } log.V(3).M(configMap).Info("configMapInformer.DeleteFunc") - }, + }), }) } @@ -483,14 +498,12 @@ func (c *Controller) addEventHandlersStatefulSet( } log.V(3).M(statefulSet).Info("statefulSetInformer.UpdateFunc") }, - DeleteFunc: func(obj interface{}) { - statefulSet := obj.(*apps.StatefulSet) + DeleteFunc: deleteHandler("statefulSetInformer.DeleteFunc", func(statefulSet *apps.StatefulSet) { if !c.isTrackedObject(&statefulSet.ObjectMeta) { return } log.V(3).M(statefulSet).Info("statefulSetInformer.DeleteFunc") - //controller.handleObject(obj) - }, + }), }) } @@ -515,14 +528,13 @@ func (c *Controller) addEventHandlersPod( log.V(3).M(newPod).Info("podInformer.UpdateFunc") c.enqueueObject(cmd_queue.NewReconcilePod(cmd_queue.ReconcileUpdate, oldPod, newPod)) }, - DeleteFunc: func(obj interface{}) { - pod := obj.(*core.Pod) + DeleteFunc: deleteHandler("podInformer.DeleteFunc", func(pod *core.Pod) { if !c.isTrackedObject(&pod.ObjectMeta) { return } log.V(3).M(pod).Info("podInformer.DeleteFunc") c.enqueueObject(cmd_queue.NewReconcilePod(cmd_queue.ReconcileDelete, pod, nil)) - }, + }), }) } diff --git a/pkg/controller/chi/controller_test.go b/pkg/controller/chi/controller_test.go index a4530d4be..841b65bf4 100644 --- a/pkg/controller/chi/controller_test.go +++ b/pkg/controller/chi/controller_test.go @@ -6,12 +6,67 @@ import ( api "github.com/altinity/clickhouse-operator/pkg/apis/clickhouse.altinity.com/v1" "github.com/altinity/clickhouse-operator/pkg/apis/common/types" "github.com/altinity/clickhouse-operator/pkg/chop" + "k8s.io/client-go/tools/cache" ) func init() { chop.New(nil, nil, "") } +func TestDeletedObject(t *testing.T) { + chi := &api.ClickHouseInstallation{} + var nilCHI *api.ClickHouseInstallation + tests := []struct { + name string + obj interface{} + want *api.ClickHouseInstallation + wantErr bool + }{ + { + name: "direct object", + obj: chi, + want: chi, + }, + { + name: "tombstone", + obj: cache.DeletedFinalStateUnknown{Obj: chi}, + want: chi, + }, + { + name: "unexpected direct object", + obj: struct{}{}, + wantErr: true, + }, + { + name: "unexpected tombstone object", + obj: cache.DeletedFinalStateUnknown{Obj: struct{}{}}, + wantErr: true, + }, + { + name: "nil tombstone object", + obj: cache.DeletedFinalStateUnknown{}, + wantErr: true, + }, + { + name: "typed nil tombstone object", + obj: cache.DeletedFinalStateUnknown{Obj: nilCHI}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := deletedObject[api.ClickHouseInstallation](tt.obj) + if (err != nil) != tt.wantErr { + t.Fatalf("deletedObject() error = %v, wantErr %v", err, tt.wantErr) + } + if got != tt.want { + t.Errorf("deletedObject() = %p, want %p", got, tt.want) + } + }) + } +} + func Test_shouldEnqueue(t *testing.T) { // NB: ShouldEnqueue intentionally does NOT pre-filter on Spec.Suspend. // The reconciler itself handles suspend (including marking CHI as Aborted when