Repository: kubean-io/kubean
Pull request #478 (2023-01-18 09:03:29)

add unit test
<!--  

Thanks for sending a pull request!  Here are some tips for you:

* make sure your commit is signed off

-->

**What type of PR is this?**
<!-- 
Uncomment only one ` /kind <>` line, hit enter to put that in a new line, and remove leading whitespaces from that line:

/kind api-change
/kind bug
/kind cleanup
/kind design
/kind documentation
/kind failing-test
/kind feature
/kind flake
-->

/kind feature

**What this PR does / why we need it**:

* add unit-test

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

**Special notes for your reviewer**:




Patch:
diff --git a/pkg/controllers/cluster/controller.go b/pkg/controllers/cluster/controller.go
index 203c7d2e9..71820e4e5 100644
--- a/pkg/controllers/cluster/controller.go
+++ b/pkg/controllers/cluster/controller.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	clusterv1alpha1 "kubean.io/api/apis/cluster/v1alpha1"
+	clusteroperationv1alpha1 "kubean.io/api/apis/clusteroperation/v1alpha1"
 	clusterClientSet "kubean.io/api/generated/cluster/clientset/versioned"
 	clusterOperationClientSet "kubean.io/api/generated/clusteroperation/clientset/versioned"
 
@@ -26,9 +27,9 @@ const (
 
 type Controller struct {
 	client.Client
-	ClientSet           *kubernetes.Clientset
-	KubeanClusterSet    *clusterClientSet.Clientset
-	KubeanClusterOpsSet *clusterOperationClientSet.Clientset
+	ClientSet           kubernetes.Interface
+	KubeanClusterSet    clusterClientSet.Interface
+	KubeanClusterOpsSet clusterOperationClientSet.Interface
 }
 
 func (c *Controller) Start(ctx context.Context) error {
@@ -78,9 +79,7 @@ func (c *Controller) UpdateStatus(cluster *clusterv1alpha1.Cluster) error {
 		return err
 	}
 	// clusterOps list sort by creation timestamp
-	sort.Slice(clusterOpsList.Items, func(i, j int) bool {
-		return clusterOpsList.Items[i].CreationTimestamp.After(clusterOpsList.Items[j].CreationTimestamp.Time)
-	})
+	c.SortClusterOperationsByCreation(clusterOpsList.Items)
 	newConditions := make([]clusterv1alpha1.ClusterCondition, 0)
 	for _, item := range clusterOpsList.Items {
 		newConditions = append(newConditions, clusterv1alpha1.ClusterCondition{
@@ -99,6 +98,13 @@ func (c *Controller) UpdateStatus(cluster *clusterv1alpha1.Cluster) error {
 	return nil
 }
 
+// SortClusterOperationsByCreation operations from large to small by creation timestamp.
+func (c *Controller) SortClusterOperationsByCreation(operations []clusteroperationv1alpha1.ClusterOperation) {
+	sort.Slice(operations, func(i, j int) bool {
+		return operations[i].CreationTimestamp.After(operations[j].CreationTimestamp.Time)
+	})
+}
+
 // CleanExcessClusterOps clean up excess ClusterOperation.
 func (c *Controller) CleanExcessClusterOps(cluster *clusterv1alpha1.Cluster) (bool, error) {
 	listOpt := metav1.ListOptions{LabelSelector: fmt.Sprintf("clusterName=%s", cluster.Name)}
@@ -110,13 +116,14 @@ func (c *Controller) CleanExcessClusterOps(cluster *clusterv1alpha1.Cluster) (bo
 		return false, nil
 	}
 
-	// clusterOps list sort by creation timestamp
-	sort.Slice(clusterOpsList.Items, func(i, j int) bool {
-		return clusterOpsList.Items[i].CreationTimestamp.After(clusterOpsList.Items[j].CreationTimestamp.Time)
-	})
+	c.SortClusterOperationsByCreation(clusterOpsList.Items)
+
 	excessClusterOpsList := clusterOpsList.Items[OpsBackupNum:]
 	for _, item := range excessClusterOpsList {
-		klog.Warningf("Delete ClusterOperation: name: %s, createTime: %s", item.Name, item.CreationTimestamp.String())
+		if item.Status.Status == clusteroperationv1alpha1.RunningStatus { // keep running job
+			continue
+		}
+		klog.Warningf("Delete ClusterOperation: name: %s, createTime: %s, status: %s", item.Name, item.CreationTimestamp.String(), item.Status.Status)
 		c.KubeanClusterOpsSet.KubeanV1alpha1().ClusterOperations().Delete(context.Background(), item.Name, metav1.DeleteOptions{})
 	}
 	return true, nil


Test patch:
diff --git a/pkg/controllers/cluster/controller_test.go b/pkg/controllers/cluster/controller_test.go
index 6714b7d17..c01d89868 100644
--- a/pkg/controllers/cluster/controller_test.go
+++ b/pkg/controllers/cluster/controller_test.go
@@ -1,12 +1,24 @@
 package cluster
 
 import (
+	"context"
+	"fmt"
+	"reflect"
 	"testing"
 	"time"
 
+	"kubean.io/api/apis"
 	clusterv1alpha1 "kubean.io/api/apis/cluster/v1alpha1"
+	clusteroperationv1alpha1 "kubean.io/api/apis/clusteroperation/v1alpha1"
+	"kubean.io/api/constants"
+	clusterv1alpha1fake "kubean.io/api/generated/cluster/clientset/versioned/fake"
+	clusteroperationv1alpha1fake "kubean.io/api/generated/clusteroperation/clientset/versioned/fake"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clientsetfake "k8s.io/client-go/kubernetes/fake"
+	"k8s.io/client-go/kubernetes/scheme"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
 func TestCompareClusterCondition(t *testing.T) {
@@ -119,3 +131,185 @@ func TestCompareClusterConditions(t *testing.T) {
 		}
 	}
 }
+
+func TestSortClusterOperationsByCreation(t *testing.T) {
+	controller := &Controller{
+		Client:              newFakeClient(),
+		ClientSet:           clientsetfake.NewSimpleClientset(),
+		KubeanClusterSet:    clusterv1alpha1fake.NewSimpleClientset(),
+		KubeanClusterOpsSet: clusteroperationv1alpha1fake.NewSimpleClientset(),
+	}
+	tests := []struct {
+		name string
+		args []clusteroperationv1alpha1.ClusterOperation
+		want []clusteroperationv1alpha1.ClusterOperation
+	}{
+		{
+			name: "empty slice",
+			args: nil,
+			want: nil,
+		},
+		{
+			name: "unsorted slice",
+			args: []clusteroperationv1alpha1.ClusterOperation{
+				{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Unix(2, 0)}},
+				{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Unix(1, 0)}},
+				{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Unix(3, 0)}},
+			},
+			want: []clusteroperationv1alpha1.ClusterOperation{
+				{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Unix(3, 0)}},
+				{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Unix(2, 0)}},
+				{ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Unix(1, 0)}},
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			controller.SortClusterOperationsByCreation(test.args)
+			if !reflect.DeepEqual(test.args, test.want) {
+				t.Fatal()
+			}
+		})
+	}
+}
+
+func Test_CleanExcessClusterOps(t *testing.T) {
+	controller := &Controller{
+		Client:              newFakeClient(),
+		ClientSet:           clientsetfake.NewSimpleClientset(),
+		KubeanClusterSet:    clusterv1alpha1fake.NewSimpleClientset(),
+		KubeanClusterOpsSet: clusteroperationv1alpha1fake.NewSimpleClientset(),
+	}
+	exampleCluster := &clusterv1alpha1.Cluster{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Cluster",
+			APIVersion: "kubean.io/v1alpha1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "cluster1",
+		},
+		Spec: clusterv1alpha1.Spec{
+			HostsConfRef: &apis.ConfigMapRef{NameSpace: "kubean-system", Name: "hosts-a"},
+			VarsConfRef:  &apis.ConfigMapRef{NameSpace: "kubean-system", Name: "vars-a"},
+		},
+	}
+	tests := []struct {
+		name string
+		args func() bool
+		want bool
+	}{
+		{
+			name: "get nothing",
+			args: func() bool {
+				result, _ := controller.CleanExcessClusterOps(exampleCluster)
+				return result
+			},
+			want: false,
+		},
+		{
+			name: "OpsBackupNum clusterOperations",
+			args: func() bool {
+				for i := 0; i < OpsBackupNum; i++ {
+					clusterOperation := &clusteroperationv1alpha1.ClusterOperation{
+						TypeMeta: metav1.TypeMeta{
+							Kind:       "ClusterOperation",
+							APIVersion: "kubean.io/v1alpha1",
+						},
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "my_kubean_ops_cluster_1_" + fmt.Sprint(i),
+							Labels:            map[string]string{constants.KubeanClusterLabelKey: "cluster1"},
+							CreationTimestamp: metav1.Unix(int64(i), 0),
+						},
+					}
+					controller.KubeanClusterOpsSet.KubeanV1alpha1().ClusterOperations().Create(context.Background(), clusterOperation, metav1.CreateOptions{})
+				}
+				result, _ := controller.CleanExcessClusterOps(exampleCluster)
+				return result
+			},
+			want: false,
+		},
+		{
+			name: "clean clusterOperations",
+			args: func() bool {
+				for i := 0; i < OpsBackupNum*2; i++ {
+					clusterOperation := &clusteroperationv1alpha1.ClusterOperation{
+						TypeMeta: metav1.TypeMeta{
+							Kind:       "ClusterOperation",
+							APIVersion: "kubean.io/v1alpha1",
+						},
+						ObjectMeta: metav1.ObjectMeta{
+							Name:              "my_kubean_ops_cluster_2_" + fmt.Sprint(i),
+							Labels:            map[string]string{constants.KubeanClusterLabelKey: "cluster1"},
+							CreationTimestamp: metav1.Unix(int64(i), 0),
+						},
+					}
+					controller.KubeanClusterOpsSet.KubeanV1alpha1().ClusterOperations().Create(context.Background(), clusterOperation, metav1.CreateOptions{})
+				}
+				result, _ := controller.CleanExcessClusterOps(exampleCluster)
+				return result
+			},
+			want: true,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if test.args() != test.want {
+				t.Fatal()
+			}
+		})
+	}
+}
+
+func Test_UpdateStatus(t *testing.T) {
+	controller := &Controller{
+		Client:              newFakeClient(),
+		ClientSet:           clientsetfake.NewSimpleClientset(),
+		KubeanClusterSet:    clusterv1alpha1fake.NewSimpleClientset(),
+		KubeanClusterOpsSet: clusteroperationv1alpha1fake.NewSimpleClientset(),
+	}
+	exampleCluster := &clusterv1alpha1.Cluster{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Cluster",
+			APIVersion: "kubean.io/v1alpha1",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "cluster1",
+		},
+		Spec: clusterv1alpha1.Spec{
+			HostsConfRef: &apis.ConfigMapRef{NameSpace: "kubean-system", Name: "hosts-a"},
+			VarsConfRef:  &apis.ConfigMapRef{NameSpace: "kubean-system", Name: "vars-a"},
+		},
+	}
+	tests := []struct {
+		name string
+		args func() bool
+		want bool
+	}{
+		{
+			name: "get nothing",
+			args: func() bool {
+				return controller.UpdateStatus(exampleCluster) == nil
+			},
+			want: true,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if test.args() != test.want {
+				t.Fatal()
+			}
+		})
+	}
+}
+
+func newFakeClient() client.Client {
+	sch := scheme.Scheme
+	if err := clusteroperationv1alpha1.AddToScheme(sch); err != nil {
+		panic(err)
+	}
+	if err := clusterv1alpha1.AddToScheme(sch); err != nil {
+		panic(err)
+	}
+	client := fake.NewClientBuilder().WithScheme(sch).WithRuntimeObjects(&clusteroperationv1alpha1.ClusterOperation{}).WithRuntimeObjects(&clusterv1alpha1.Cluster{}).Build()
+	return client
+}