This is an automated email from the ASF dual-hosted git repository.
jimin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-seata-k8s.git
The following commit(s) were added to refs/heads/master by this push:
new 1d240de feature: support finalizer (#44)
1d240de is described below
commit 1d240de1546a0abc9b38eff6ec7bab7543d6ba73
Author: jimin <[email protected]>
AuthorDate: Wed Dec 10 10:41:28 2025 +0800
feature: support finalizer (#44)
---
go.mod | 1 +
pkg/finalizer/cleanup_handler.go | 229 +++++++++++++++++++++++
pkg/finalizer/finalizer_manager.go | 197 ++++++++++++++++++++
pkg/finalizer/finalizer_manager_test.go | 312 ++++++++++++++++++++++++++++++++
4 files changed, 739 insertions(+)
diff --git a/go.mod b/go.mod
index 9c493d7..57df014 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
+ github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.7.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
diff --git a/pkg/finalizer/cleanup_handler.go b/pkg/finalizer/cleanup_handler.go
new file mode 100644
index 0000000..4cbe7ca
--- /dev/null
+++ b/pkg/finalizer/cleanup_handler.go
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package finalizer
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/go-logr/logr"
+ apiv1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/labels"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ seatav1 "github.com/apache/seata-k8s/api/v1"
+ seatav1alpha1 "github.com/apache/seata-k8s/api/v1alpha1"
+)
+
+// CleanupHandler handles cleanup operations when SeataServer is deleted
+type CleanupHandler struct {
+ Client client.Client
+ Log logr.Logger
+}
+
+// NewCleanupHandler creates a new CleanupHandler instance
+func NewCleanupHandler(client client.Client, log logr.Logger) *CleanupHandler {
+ return &CleanupHandler{
+ Client: client,
+ Log: log,
+ }
+}
+
+// HandleCleanup performs cleanup operations based on the SeataServer
configuration
+func (ch *CleanupHandler) HandleCleanup(ctx context.Context, server
interface{}) error {
+ switch s := server.(type) {
+ case *seatav1.SeataServer:
+ return ch.handleCleanupV1(ctx, s)
+ case *seatav1alpha1.SeataServer:
+ return ch.handleCleanupV1Alpha1(ctx, s)
+ default:
+ return fmt.Errorf("unsupported type: %T", server)
+ }
+}
+
+// handleCleanupV1 performs cleanup for v1 SeataServer
+func (ch *CleanupHandler) handleCleanupV1(ctx context.Context, s
*seatav1.SeataServer) error {
+ ch.Log.Info("Starting cleanup for SeataServer", "name", s.Name,
"namespace", s.Namespace)
+
+ // Only cleanup PVCs if reclaim policy is Delete
+ if s.Spec.Persistence.VolumeReclaimPolicy ==
seatav1.VolumeReclaimPolicyDelete {
+ ch.Log.Info("Cleaning up PVCs for SeataServer", "name", s.Name,
"namespace", s.Namespace)
+ if err := ch.cleanupAllPVCs(ctx, s.Name, s.Namespace, s.UID);
err != nil {
+ ch.Log.Error(err, "Failed to cleanup PVCs",
"seataserver", s.Name, "namespace", s.Namespace)
+ return err
+ }
+ }
+
+ // Cleanup other resources as needed
+ if err := ch.cleanupConfigMaps(ctx, s.Name, s.Namespace); err != nil {
+ ch.Log.Error(err, "Failed to cleanup ConfigMaps",
"seataserver", s.Name, "namespace", s.Namespace)
+ // Continue cleanup despite error
+ }
+
+ if err := ch.cleanupSecrets(ctx, s.Name, s.Namespace); err != nil {
+ ch.Log.Error(err, "Failed to cleanup Secrets", "seataserver",
s.Name, "namespace", s.Namespace)
+ // Continue cleanup despite error
+ }
+
+ ch.Log.Info("Cleanup completed for SeataServer", "name", s.Name,
"namespace", s.Namespace)
+ return nil
+}
+
+// handleCleanupV1Alpha1 performs cleanup for v1alpha1 SeataServer
+func (ch *CleanupHandler) handleCleanupV1Alpha1(ctx context.Context, s
*seatav1alpha1.SeataServer) error {
+ ch.Log.Info("Starting cleanup for SeataServer (v1alpha1)", "name",
s.Name, "namespace", s.Namespace)
+
+ // Only cleanup PVCs if reclaim policy is Delete
+ if s.Spec.Persistence.VolumeReclaimPolicy ==
seatav1alpha1.VolumeReclaimPolicyDelete {
+ ch.Log.Info("Cleaning up PVCs for SeataServer (v1alpha1)",
"name", s.Name, "namespace", s.Namespace)
+ if err := ch.cleanupAllPVCs(ctx, s.Name, s.Namespace, s.UID);
err != nil {
+ ch.Log.Error(err, "Failed to cleanup PVCs",
"seataserver", s.Name, "namespace", s.Namespace)
+ return err
+ }
+ }
+
+ // Cleanup other resources as needed
+ if err := ch.cleanupConfigMaps(ctx, s.Name, s.Namespace); err != nil {
+ ch.Log.Error(err, "Failed to cleanup ConfigMaps",
"seataserver", s.Name, "namespace", s.Namespace)
+ // Continue cleanup despite error
+ }
+
+ if err := ch.cleanupSecrets(ctx, s.Name, s.Namespace); err != nil {
+ ch.Log.Error(err, "Failed to cleanup Secrets", "seataserver",
s.Name, "namespace", s.Namespace)
+ // Continue cleanup despite error
+ }
+
+ ch.Log.Info("Cleanup completed for SeataServer (v1alpha1)", "name",
s.Name, "namespace", s.Namespace)
+ return nil
+}
+
+// cleanupAllPVCs deletes all PVCs associated with the SeataServer
+func (ch *CleanupHandler) cleanupAllPVCs(ctx context.Context, name, namespace
string, uid interface{}) error {
+ pvcList, err := ch.getPVCList(ctx, name, namespace, uid)
+ if err != nil {
+ if errors.IsNotFound(err) {
+ return nil // PVCs not found, no cleanup needed
+ }
+ return err
+ }
+
+ ch.Log.Info("Found PVCs to cleanup", "count", len(pvcList.Items),
"seataserver", name, "namespace", namespace)
+
+ for _, pvc := range pvcList.Items {
+ if err := ch.deletePVC(ctx, &pvc); err != nil {
+ ch.Log.Error(err, "Failed to delete PVC", "pvc",
pvc.Name, "namespace", pvc.Namespace)
+ // Continue deleting other PVCs despite error
+ }
+ }
+
+ return nil
+}
+
+// getPVCList retrieves all PVCs associated with the SeataServer
+func (ch *CleanupHandler) getPVCList(ctx context.Context, name, namespace
string, uid interface{}) (*apiv1.PersistentVolumeClaimList, error) {
+ selector := labels.SelectorFromSet(map[string]string{
+ "app": name,
+ "uid": fmt.Sprintf("%v", uid),
+ })
+
+ pvcList := &apiv1.PersistentVolumeClaimList{}
+ listOpts := &client.ListOptions{
+ Namespace: namespace,
+ LabelSelector: selector,
+ }
+
+ if err := ch.Client.List(ctx, pvcList, listOpts); err != nil {
+ ch.Log.Error(err, "Failed to list PVCs", "seataserver", name,
"namespace", namespace)
+ return nil, err
+ }
+
+ return pvcList, nil
+}
+
+// deletePVC deletes a single PVC
+func (ch *CleanupHandler) deletePVC(ctx context.Context, pvc
*apiv1.PersistentVolumeClaim) error {
+ ch.Log.Info("Deleting PVC", "pvc", pvc.Name, "namespace", pvc.Namespace)
+
+ if err := ch.Client.Delete(ctx, pvc); err != nil {
+ if !errors.IsNotFound(err) {
+ ch.Log.Error(err, "Failed to delete PVC", "pvc",
pvc.Name, "namespace", pvc.Namespace)
+ return err
+ }
+ }
+
+ return nil
+}
+
+// cleanupConfigMaps deletes ConfigMaps associated with the SeataServer
+func (ch *CleanupHandler) cleanupConfigMaps(ctx context.Context, name,
namespace string) error {
+ cmList := &apiv1.ConfigMapList{}
+ selector := labels.SelectorFromSet(map[string]string{"app": name})
+ listOpts := &client.ListOptions{
+ Namespace: namespace,
+ LabelSelector: selector,
+ }
+
+ if err := ch.Client.List(ctx, cmList, listOpts); err != nil {
+ if !errors.IsNotFound(err) {
+ return err
+ }
+ return nil
+ }
+
+ for _, cm := range cmList.Items {
+ ch.Log.Info("Deleting ConfigMap", "configmap", cm.Name,
"namespace", cm.Namespace)
+ if err := ch.Client.Delete(ctx, &cm); err != nil {
+ if !errors.IsNotFound(err) {
+ ch.Log.Error(err, "Failed to delete ConfigMap",
"configmap", cm.Name, "namespace", cm.Namespace)
+ // Continue cleanup despite error
+ }
+ }
+ }
+
+ return nil
+}
+
+// cleanupSecrets deletes Secrets associated with the SeataServer
+func (ch *CleanupHandler) cleanupSecrets(ctx context.Context, name, namespace
string) error {
+ secretList := &apiv1.SecretList{}
+ selector := labels.SelectorFromSet(map[string]string{"app": name})
+ listOpts := &client.ListOptions{
+ Namespace: namespace,
+ LabelSelector: selector,
+ }
+
+ if err := ch.Client.List(ctx, secretList, listOpts); err != nil {
+ if !errors.IsNotFound(err) {
+ return err
+ }
+ return nil
+ }
+
+ for _, secret := range secretList.Items {
+ ch.Log.Info("Deleting Secret", "secret", secret.Name,
"namespace", secret.Namespace)
+ if err := ch.Client.Delete(ctx, &secret); err != nil {
+ if !errors.IsNotFound(err) {
+ ch.Log.Error(err, "Failed to delete Secret",
"secret", secret.Name, "namespace", secret.Namespace)
+ // Continue cleanup despite error
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/finalizer/finalizer_manager.go
b/pkg/finalizer/finalizer_manager.go
new file mode 100644
index 0000000..1dbe49b
--- /dev/null
+++ b/pkg/finalizer/finalizer_manager.go
@@ -0,0 +1,197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package finalizer
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/go-logr/logr"
+ "k8s.io/apimachinery/pkg/types"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ seatav1 "github.com/apache/seata-k8s/api/v1"
+ seatav1alpha1 "github.com/apache/seata-k8s/api/v1alpha1"
+ "github.com/apache/seata-k8s/pkg/utils"
+)
+
+const (
+ // SeataFinalizerName is the name of the finalizer for SeataServer
resources
+ SeataFinalizerName =
"seataserver.finalizer.operator.seata.apache.org/cleanup"
+
+ // DeprecatedSeataFinalizerName is the old finalizer name for backward
compatibility
+ DeprecatedSeataFinalizerName = "seataserver.finalizer"
+)
+
+// FinalizerManager manages the lifecycle of SeataServer resources through
finalizers
+type FinalizerManager struct {
+ Client client.Client
+ Log logr.Logger
+}
+
+// NewFinalizerManager creates a new FinalizerManager instance
+func NewFinalizerManager(client client.Client, log logr.Logger)
*FinalizerManager {
+ return &FinalizerManager{
+ Client: client,
+ Log: log,
+ }
+}
+
+// AddFinalizer adds the finalizer to the SeataServer object if not already
present
+func (fm *FinalizerManager) AddFinalizer(ctx context.Context, server
interface{}, finalizerName string) error {
+ switch s := server.(type) {
+ case *seatav1.SeataServer:
+ return fm.addFinalizerToV1(ctx, s, finalizerName)
+ case *seatav1alpha1.SeataServer:
+ return fm.addFinalizerToV1Alpha1(ctx, s, finalizerName)
+ default:
+ return fmt.Errorf("unsupported type: %T", server)
+ }
+}
+
+// RemoveFinalizer removes the finalizer from the SeataServer object
+func (fm *FinalizerManager) RemoveFinalizer(ctx context.Context, server
interface{}, finalizerName string) error {
+ switch s := server.(type) {
+ case *seatav1.SeataServer:
+ return fm.removeFinalizerFromV1(ctx, s, finalizerName)
+ case *seatav1alpha1.SeataServer:
+ return fm.removeFinalizerFromV1Alpha1(ctx, s, finalizerName)
+ default:
+ return fmt.Errorf("unsupported type: %T", server)
+ }
+}
+
+// HasFinalizer checks if the SeataServer has the specified finalizer
+func (fm *FinalizerManager) HasFinalizer(server interface{}, finalizerName
string) bool {
+ switch s := server.(type) {
+ case *seatav1.SeataServer:
+ return utils.ContainsString(s.ObjectMeta.Finalizers,
finalizerName)
+ case *seatav1alpha1.SeataServer:
+ return utils.ContainsString(s.ObjectMeta.Finalizers,
finalizerName)
+ default:
+ return false
+ }
+}
+
+// IsBeingDeleted checks if the SeataServer is marked for deletion
+func (fm *FinalizerManager) IsBeingDeleted(server interface{}) bool {
+ switch s := server.(type) {
+ case *seatav1.SeataServer:
+ return !s.DeletionTimestamp.IsZero()
+ case *seatav1alpha1.SeataServer:
+ return !s.DeletionTimestamp.IsZero()
+ default:
+ return false
+ }
+}
+
+// GetDeletionTimestamp returns the deletion timestamp of the SeataServer
+func (fm *FinalizerManager) GetDeletionTimestamp(server interface{})
interface{} {
+ switch s := server.(type) {
+ case *seatav1.SeataServer:
+ return s.DeletionTimestamp
+ case *seatav1alpha1.SeataServer:
+ return s.DeletionTimestamp
+ default:
+ return nil
+ }
+}
+
+// GetNamespacedName returns the NamespacedName of the SeataServer
+func (fm *FinalizerManager) GetNamespacedName(server interface{})
types.NamespacedName {
+ switch s := server.(type) {
+ case *seatav1.SeataServer:
+ return types.NamespacedName{Name: s.Name, Namespace:
s.Namespace}
+ case *seatav1alpha1.SeataServer:
+ return types.NamespacedName{Name: s.Name, Namespace:
s.Namespace}
+ default:
+ return types.NamespacedName{}
+ }
+}
+
+// Helper methods for v1 API
+
+func (fm *FinalizerManager) addFinalizerToV1(ctx context.Context, s
*seatav1.SeataServer, finalizerName string) error {
+ if utils.ContainsString(s.ObjectMeta.Finalizers, finalizerName) {
+ return nil // Already has finalizer
+ }
+
+ fm.Log.Info("Adding finalizer", "name", finalizerName, "seataserver",
s.Name, "namespace", s.Namespace)
+
+ s.ObjectMeta.Finalizers = append(s.ObjectMeta.Finalizers, finalizerName)
+ if err := fm.Client.Update(ctx, s); err != nil {
+ fm.Log.Error(err, "Failed to add finalizer", "seataserver",
s.Name, "namespace", s.Namespace)
+ return err
+ }
+
+ fm.Log.Info("Finalizer added successfully", "name", finalizerName,
"seataserver", s.Name, "namespace", s.Namespace)
+ return nil
+}
+
+func (fm *FinalizerManager) removeFinalizerFromV1(ctx context.Context, s
*seatav1.SeataServer, finalizerName string) error {
+ if !utils.ContainsString(s.ObjectMeta.Finalizers, finalizerName) {
+ return nil // Finalizer already removed
+ }
+
+ fm.Log.Info("Removing finalizer", "name", finalizerName, "seataserver",
s.Name, "namespace", s.Namespace)
+
+ s.ObjectMeta.Finalizers = utils.RemoveString(s.ObjectMeta.Finalizers,
finalizerName)
+ if err := fm.Client.Update(ctx, s); err != nil {
+ fm.Log.Error(err, "Failed to remove finalizer", "seataserver",
s.Name, "namespace", s.Namespace)
+ return err
+ }
+
+ fm.Log.Info("Finalizer removed successfully", "name", finalizerName,
"seataserver", s.Name, "namespace", s.Namespace)
+ return nil
+}
+
+// Helper methods for v1alpha1 API
+
+func (fm *FinalizerManager) addFinalizerToV1Alpha1(ctx context.Context, s
*seatav1alpha1.SeataServer, finalizerName string) error {
+ if utils.ContainsString(s.ObjectMeta.Finalizers, finalizerName) {
+ return nil // Already has finalizer
+ }
+
+ fm.Log.Info("Adding finalizer", "name", finalizerName, "seataserver",
s.Name, "namespace", s.Namespace)
+
+ s.ObjectMeta.Finalizers = append(s.ObjectMeta.Finalizers, finalizerName)
+ if err := fm.Client.Update(ctx, s); err != nil {
+ fm.Log.Error(err, "Failed to add finalizer", "seataserver",
s.Name, "namespace", s.Namespace)
+ return err
+ }
+
+ fm.Log.Info("Finalizer added successfully", "name", finalizerName,
"seataserver", s.Name, "namespace", s.Namespace)
+ return nil
+}
+
+func (fm *FinalizerManager) removeFinalizerFromV1Alpha1(ctx context.Context, s
*seatav1alpha1.SeataServer, finalizerName string) error {
+ if !utils.ContainsString(s.ObjectMeta.Finalizers, finalizerName) {
+ return nil // Finalizer already removed
+ }
+
+ fm.Log.Info("Removing finalizer", "name", finalizerName, "seataserver",
s.Name, "namespace", s.Namespace)
+
+ s.ObjectMeta.Finalizers = utils.RemoveString(s.ObjectMeta.Finalizers,
finalizerName)
+ if err := fm.Client.Update(ctx, s); err != nil {
+ fm.Log.Error(err, "Failed to remove finalizer", "seataserver",
s.Name, "namespace", s.Namespace)
+ return err
+ }
+
+ fm.Log.Info("Finalizer removed successfully", "name", finalizerName,
"seataserver", s.Name, "namespace", s.Namespace)
+ return nil
+}
diff --git a/pkg/finalizer/finalizer_manager_test.go
b/pkg/finalizer/finalizer_manager_test.go
new file mode 100644
index 0000000..8e8f290
--- /dev/null
+++ b/pkg/finalizer/finalizer_manager_test.go
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package finalizer
+
+import (
+ "context"
+ seatav1 "github.com/apache/seata-k8s/api/v1"
+ "github.com/go-logr/logr"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
+ "testing"
+ "time"
+)
+
+func TestFinalizerManager_AddFinalizer(t *testing.T) {
+ testCases := []struct {
+ name string
+ seataServer *seatav1.SeataServer
+ finalizer string
+ expectError bool
+ }{
+ {
+ name: "add finalizer to seataserver without finalizers",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ Finalizers: []string{},
+ },
+ },
+ finalizer: SeataFinalizerName,
+ expectError: false,
+ },
+ {
+ name: "add finalizer to seataserver with existing
finalizers",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ Finalizers: []string{"other-finalizer"},
+ },
+ },
+ finalizer: SeataFinalizerName,
+ expectError: false,
+ },
+ {
+ name: "add duplicate finalizer",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ Finalizers:
[]string{SeataFinalizerName},
+ },
+ },
+ finalizer: SeataFinalizerName,
+ expectError: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ scheme := createTestScheme()
+ fakeClient := fake.NewClientBuilder().
+ WithScheme(scheme).
+ WithObjects(tc.seataServer).
+ Build()
+
+ fm := &FinalizerManager{
+ Client: fakeClient,
+ Log: logr.Discard(),
+ }
+
+ ctx := context.Background()
+ err := fm.AddFinalizer(ctx, tc.seataServer,
tc.finalizer)
+
+ if (err != nil) != tc.expectError {
+ t.Errorf("expected error: %v, got: %v",
tc.expectError, err)
+ }
+
+ if !tc.expectError {
+ if !fm.HasFinalizer(tc.seataServer,
tc.finalizer) {
+ t.Errorf("finalizer %s not found after
adding", tc.finalizer)
+ }
+ }
+ })
+ }
+}
+
+func TestFinalizerManager_RemoveFinalizer(t *testing.T) {
+ testCases := []struct {
+ name string
+ seataServer *seatav1.SeataServer
+ finalizer string
+ expectError bool
+ expectFound bool
+ }{
+ {
+ name: "remove existing finalizer",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ Finalizers:
[]string{SeataFinalizerName, "other-finalizer"},
+ },
+ },
+ finalizer: SeataFinalizerName,
+ expectError: false,
+ expectFound: false,
+ },
+ {
+ name: "remove non-existing finalizer",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ Finalizers: []string{"other-finalizer"},
+ },
+ },
+ finalizer: SeataFinalizerName,
+ expectError: false,
+ expectFound: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ scheme := createTestScheme()
+ fakeClient := fake.NewClientBuilder().
+ WithScheme(scheme).
+ WithObjects(tc.seataServer).
+ Build()
+
+ fm := &FinalizerManager{
+ Client: fakeClient,
+ Log: logr.Discard(),
+ }
+
+ ctx := context.Background()
+ err := fm.RemoveFinalizer(ctx, tc.seataServer,
tc.finalizer)
+
+ if (err != nil) != tc.expectError {
+ t.Errorf("expected error: %v, got: %v",
tc.expectError, err)
+ }
+
+ hasFinalizer := fm.HasFinalizer(tc.seataServer,
tc.finalizer)
+ if hasFinalizer != tc.expectFound {
+ t.Errorf("expected finalizer found: %v, got:
%v", tc.expectFound, hasFinalizer)
+ }
+ })
+ }
+}
+
+func TestFinalizerManager_HasFinalizer(t *testing.T) {
+ testCases := []struct {
+ name string
+ seataServer *seatav1.SeataServer
+ finalizer string
+ expected bool
+ }{
+ {
+ name: "finalizer exists",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ Finalizers:
[]string{SeataFinalizerName},
+ },
+ },
+ finalizer: SeataFinalizerName,
+ expected: true,
+ },
+ {
+ name: "finalizer does not exist",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ Finalizers: []string{"other-finalizer"},
+ },
+ },
+ finalizer: SeataFinalizerName,
+ expected: false,
+ },
+ {
+ name: "no finalizers",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ Finalizers: []string{},
+ },
+ },
+ finalizer: SeataFinalizerName,
+ expected: false,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ fm := &FinalizerManager{
+ Client: nil,
+ Log: logr.Discard(),
+ }
+
+ result := fm.HasFinalizer(tc.seataServer, tc.finalizer)
+ if result != tc.expected {
+ t.Errorf("expected %v, got %v", tc.expected,
result)
+ }
+ })
+ }
+}
+
+func TestFinalizerManager_IsBeingDeleted(t *testing.T) {
+ testCases := []struct {
+ name string
+ seataServer *seatav1.SeataServer
+ expected bool
+ }{
+ {
+ name: "seataserver not being deleted",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ },
+ },
+ expected: false,
+ },
+ {
+ name: "seataserver not being deleted",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ DeletionTimestamp: &metav1.Time{Time:
time.Time{}},
+ },
+ },
+ expected: false,
+ },
+ {
+ name: "seataserver being deleted",
+ seataServer: &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ DeletionTimestamp: &metav1.Time{
+ Time: time.Now(),
+ },
+ },
+ },
+ expected: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ fm := &FinalizerManager{
+ Client: nil,
+ Log: logr.Discard(),
+ }
+
+ result := fm.IsBeingDeleted(tc.seataServer)
+ if result != tc.expected {
+ t.Errorf("expected %v, got %v", tc.expected,
result)
+ }
+ })
+ }
+}
+
+func TestFinalizerManager_GetNamespacedName(t *testing.T) {
+ seataServer := &seatav1.SeataServer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-seata",
+ Namespace: "default",
+ },
+ }
+
+ fm := &FinalizerManager{
+ Client: nil,
+ Log: logr.Discard(),
+ }
+
+ result := fm.GetNamespacedName(seataServer)
+ expected := types.NamespacedName{Name: "test-seata", Namespace:
"default"}
+
+ if result != expected {
+ t.Errorf("expected %v, got %v", expected, result)
+ }
+}
+
+func createTestScheme() *runtime.Scheme {
+ scheme := fake.NewClientBuilder().Build().Scheme()
+ _ = seatav1.AddToScheme(scheme)
+ return scheme
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]