Merge pull request #5467 from xovoxy/cluster-ut

add test for pkg/controllers/cluster
This commit is contained in:
karmada-bot 2024-09-09 20:21:56 +08:00 committed by GitHub
commit 54b90a2ff2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 1000 additions and 0 deletions

View File

@ -0,0 +1,467 @@
/*
Copyright 2024 The Karmada Authors.
Licensed 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 cluster
import (
"context"
"reflect"
"testing"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
workv1alpha1 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/gclient"
"github.com/karmada-io/karmada/pkg/util/names"
)
func newClusterController() *Controller {
rbIndexerFunc := func(obj client.Object) []string {
rb, ok := obj.(*workv1alpha2.ResourceBinding)
if !ok {
return nil
}
return util.GetBindingClusterNames(&rb.Spec)
}
crbIndexerFunc := func(obj client.Object) []string {
crb, ok := obj.(*workv1alpha2.ClusterResourceBinding)
if !ok {
return nil
}
return util.GetBindingClusterNames(&crb.Spec)
}
client := fake.NewClientBuilder().WithScheme(gclient.NewSchema()).
WithIndex(&workv1alpha2.ResourceBinding{}, rbClusterKeyIndex, rbIndexerFunc).
WithIndex(&workv1alpha2.ClusterResourceBinding{}, crbClusterKeyIndex, crbIndexerFunc).
WithStatusSubresource(&clusterv1alpha1.Cluster{}).Build()
return &Controller{
Client: client,
EventRecorder: record.NewFakeRecorder(1024),
clusterHealthMap: newClusterHealthMap(),
EnableTaintManager: true,
ClusterMonitorGracePeriod: 40 * time.Second,
}
}
func TestController_Reconcile(t *testing.T) {
req := controllerruntime.Request{NamespacedName: types.NamespacedName{Name: "test-cluster"}}
tests := []struct {
name string
cluster *clusterv1alpha1.Cluster
ns *corev1.Namespace
work *workv1alpha1.Work
del bool
wCluster *clusterv1alpha1.Cluster
want controllerruntime.Result
wantErr bool
}{
{
name: "cluster without status",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
},
wCluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{{
Key: clusterv1alpha1.TaintClusterNotReady,
Effect: corev1.TaintEffectNoSchedule,
}},
},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{},
},
},
want: controllerruntime.Result{},
wantErr: false,
},
{
name: "cluster with ready condition",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{{
Key: clusterv1alpha1.TaintClusterNotReady,
Effect: corev1.TaintEffectNoSchedule,
}},
},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionTrue,
},
},
},
},
wCluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{Taints: []corev1.Taint{}},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionTrue,
},
},
},
},
want: controllerruntime.Result{},
wantErr: false,
},
{
name: "cluster with unknown condition",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{{
Key: clusterv1alpha1.TaintClusterNotReady,
Effect: corev1.TaintEffectNoSchedule,
}},
},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionUnknown,
},
},
},
},
wCluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{Taints: []corev1.Taint{
{
Key: clusterv1alpha1.TaintClusterUnreachable,
Effect: corev1.TaintEffectNoSchedule,
},
}},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionUnknown,
},
},
},
},
want: controllerruntime.Result{},
wantErr: false,
},
{
name: "cluster with false condition",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{{
Key: clusterv1alpha1.TaintClusterUnreachable,
Effect: corev1.TaintEffectNoSchedule,
}},
},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionFalse,
},
},
},
},
wCluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{Taints: []corev1.Taint{
{
Key: clusterv1alpha1.TaintClusterNotReady,
Effect: corev1.TaintEffectNoSchedule,
},
}},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionFalse,
},
},
},
},
want: controllerruntime.Result{},
wantErr: false,
},
{
name: "cluster not found",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster-noexist",
Finalizers: []string{util.ClusterControllerFinalizer},
},
},
want: controllerruntime.Result{},
wantErr: false,
},
{
name: "remove cluster failed",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
SyncMode: clusterv1alpha1.Pull,
},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionFalse,
},
},
},
},
ns: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: names.GenerateExecutionSpaceName("test-cluster"),
},
},
work: &workv1alpha1.Work{
ObjectMeta: metav1.ObjectMeta{
Name: "test-work",
Namespace: names.GenerateExecutionSpaceName("test-cluster"),
Finalizers: []string{util.ExecutionControllerFinalizer},
},
},
del: true,
want: controllerruntime.Result{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := newClusterController()
if tt.cluster != nil {
if err := c.Create(context.Background(), tt.cluster, &client.CreateOptions{}); err != nil {
t.Fatalf("faild to create cluster %v", err)
}
}
if tt.ns != nil {
if err := c.Create(context.Background(), tt.ns, &client.CreateOptions{}); err != nil {
t.Fatalf("faild to create ns %v", err)
}
}
if tt.work != nil {
if err := c.Create(context.Background(), tt.work, &client.CreateOptions{}); err != nil {
t.Fatalf("faild to create work %v", err)
}
}
if tt.del {
if err := c.Delete(context.Background(), tt.cluster, &client.DeleteOptions{}); err != nil {
t.Fatalf("failed to delete cluster %v", err)
}
}
got, err := c.Reconcile(context.Background(), req)
if (err != nil) != tt.wantErr {
t.Errorf("Controller.Reconcile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Controller.Reconcile() = %v, want %v", got, tt.want)
return
}
if tt.wCluster != nil {
cluster := &clusterv1alpha1.Cluster{}
if err := c.Get(context.Background(), types.NamespacedName{Name: tt.cluster.Name}, cluster, &client.GetOptions{}); err != nil {
t.Errorf("failed to get cluster %v", err)
return
}
cleanUpCluster(cluster)
if !reflect.DeepEqual(cluster, tt.wCluster) {
t.Errorf("Cluster resource reconcile get %v, want %v", *cluster, *tt.wCluster)
}
}
})
}
}
func TestController_monitorClusterHealth(t *testing.T) {
tests := []struct {
name string
cluster *clusterv1alpha1.Cluster
wCluster *clusterv1alpha1.Cluster
wantErr bool
}{
{
name: "cluster without status",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
SyncMode: clusterv1alpha1.Pull,
},
},
wCluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
SyncMode: clusterv1alpha1.Pull,
Taints: []corev1.Taint{
{
Key: clusterv1alpha1.TaintClusterUnreachable,
Effect: corev1.TaintEffectNoExecute,
},
},
},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionUnknown,
Reason: "ClusterStatusNeverUpdated",
Message: "Cluster status controller never posted cluster status.",
}},
},
},
wantErr: false,
},
{
name: "cluster with ready condition",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
SyncMode: clusterv1alpha1.Pull,
},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionTrue,
},
},
},
},
wCluster: &clusterv1alpha1.Cluster{
ObjectMeta: controllerruntime.ObjectMeta{
Name: "test-cluster",
Finalizers: []string{util.ClusterControllerFinalizer},
},
Spec: clusterv1alpha1.ClusterSpec{
SyncMode: clusterv1alpha1.Pull,
Taints: []corev1.Taint{},
},
Status: clusterv1alpha1.ClusterStatus{
Conditions: []metav1.Condition{
{
Type: clusterv1alpha1.ClusterConditionReady,
Status: metav1.ConditionTrue,
},
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := newClusterController()
if tt.cluster != nil {
if err := c.Create(context.Background(), tt.cluster, &client.CreateOptions{}); err != nil {
t.Fatalf("failed to create cluster: %v", err)
}
}
if err := c.monitorClusterHealth(context.Background()); (err != nil) != tt.wantErr {
t.Errorf("Controller.monitorClusterHealth() error = %v, wantErr %v", err, tt.wantErr)
return
}
cluster := &clusterv1alpha1.Cluster{}
if err := c.Get(context.Background(), types.NamespacedName{Name: "test-cluster"}, cluster, &client.GetOptions{}); err != nil {
t.Errorf("failed to get cluster: %v", err)
return
}
cleanUpCluster(cluster)
if !reflect.DeepEqual(cluster, tt.wCluster) {
t.Errorf("Cluster resource get %+v, want %+v", *cluster, *tt.wCluster)
return
}
})
}
}
// cleanUpCluster removes unnecessary fields from Cluster resource for testing purposes.
func cleanUpCluster(c *clusterv1alpha1.Cluster) {
c.ObjectMeta.ResourceVersion = ""
taints := []corev1.Taint{}
for _, taint := range c.Spec.Taints {
taint.TimeAdded = nil
taints = append(taints, taint)
}
c.Spec.Taints = taints
cond := []metav1.Condition{}
for _, condition := range c.Status.Conditions {
condition.LastTransitionTime = metav1.Time{}
cond = append(cond, condition)
}
c.Status.Conditions = cond
}

View File

@ -0,0 +1,533 @@
/*
Copyright 2024 The Karmada Authors.
Licensed 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 cluster
import (
"context"
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/fedinformer/keys"
"github.com/karmada-io/karmada/pkg/util/gclient"
)
func newNoExecuteTaintManager() *NoExecuteTaintManager {
rbIndexerFunc := func(obj client.Object) []string {
rb, ok := obj.(*workv1alpha2.ResourceBinding)
if !ok {
return nil
}
return util.GetBindingClusterNames(&rb.Spec)
}
crbIndexerFunc := func(obj client.Object) []string {
crb, ok := obj.(*workv1alpha2.ClusterResourceBinding)
if !ok {
return nil
}
return util.GetBindingClusterNames(&crb.Spec)
}
mgr := &NoExecuteTaintManager{
Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).
WithIndex(&workv1alpha2.ResourceBinding{}, rbClusterKeyIndex, rbIndexerFunc).
WithIndex(&workv1alpha2.ClusterResourceBinding{}, crbClusterKeyIndex, crbIndexerFunc).Build(),
}
bindingEvictionWorkerOptions := util.Options{
Name: "binding-eviction",
KeyFunc: nil,
ReconcileFunc: mgr.syncBindingEviction,
}
mgr.bindingEvictionWorker = util.NewAsyncWorker(bindingEvictionWorkerOptions)
clusterBindingEvictionWorkerOptions := util.Options{
Name: "cluster-binding-eviction",
KeyFunc: nil,
ReconcileFunc: mgr.syncClusterBindingEviction,
}
mgr.clusterBindingEvictionWorker = util.NewAsyncWorker(clusterBindingEvictionWorkerOptions)
return mgr
}
func TestNoExecuteTaintManager_Reconcile(t *testing.T) {
tests := []struct {
name string
cluster *clusterv1alpha1.Cluster
want reconcile.Result
wantErr bool
}{
{
name: "no taints",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
Spec: clusterv1alpha1.ClusterSpec{},
},
want: reconcile.Result{},
wantErr: false,
},
{
name: "have taints",
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{
{
Key: "test-taint",
Value: "test-value",
Effect: corev1.TaintEffectNoExecute,
},
},
},
},
want: reconcile.Result{},
wantErr: false,
},
{
name: "cluster not found",
want: reconcile.Result{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tc := newNoExecuteTaintManager()
if err := tc.Client.Create(context.Background(), &workv1alpha2.ResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-rb",
Namespace: "default",
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
}, &client.CreateOptions{}); err != nil {
t.Fatalf("failed to create rb, %v", err)
}
if err := tc.Client.Create(context.Background(), &workv1alpha2.ClusterResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ClusterResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-crb",
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
}, &client.CreateOptions{}); err != nil {
t.Fatalf("failed to create crb, %v", err)
}
if tt.cluster != nil {
if err := tc.Client.Create(context.Background(), tt.cluster, &client.CreateOptions{}); err != nil {
t.Fatal(err)
return
}
}
req := reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-cluster"}}
got, err := tc.Reconcile(context.Background(), req)
if (err != nil) != tt.wantErr {
t.Errorf("NoExecuteTaintManager.Reconcile() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NoExecuteTaintManager.Reconcile() = %v, want %v", got, tt.want)
}
})
}
}
func TestNoExecuteTaintManager_syncBindingEviction(t *testing.T) {
replica := int32(1)
tests := []struct {
name string
rb *workv1alpha2.ResourceBinding
cluster *clusterv1alpha1.Cluster
wrb *workv1alpha2.ResourceBinding
wantErr bool
}{
{
name: "rb without tolerations",
rb: &workv1alpha2.ResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-rb",
Namespace: "default",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
},
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{
{
Key: "cluster.karmada.io/not-ready",
Effect: corev1.TaintEffectNoExecute,
},
},
},
},
wrb: &workv1alpha2.ResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-rb",
Namespace: "default",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
GracefulEvictionTasks: []workv1alpha2.GracefulEvictionTask{
{
FromCluster: "test-cluster",
Replicas: &replica,
Reason: workv1alpha2.EvictionReasonTaintUntolerated,
Producer: workv1alpha2.EvictionProducerTaintManager,
},
},
},
},
wantErr: false,
},
{
name: "rb with tolerations",
rb: &workv1alpha2.ResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-rb",
Namespace: "default",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":30},{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
},
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{
{
Key: "cluster.karmada.io/not-ready",
Effect: corev1.TaintEffectNoExecute,
},
},
},
},
wrb: &workv1alpha2.ResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-rb",
Namespace: "default",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
},
wantErr: false,
},
{
name: "rb not exist",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tc := newNoExecuteTaintManager()
if tt.rb != nil {
if err := tc.Create(context.Background(), tt.rb, &client.CreateOptions{}); err != nil {
t.Fatalf("failed to create rb: %v", err)
}
}
if tt.cluster != nil {
if err := tc.Create(context.Background(), tt.cluster, &client.CreateOptions{}); err != nil {
t.Fatalf("failed to create cluster: %v", err)
}
}
key := keys.FederatedKey{
Cluster: "test-cluster",
ClusterWideKey: keys.ClusterWideKey{
Kind: "ResourceBinding",
Name: "test-rb",
Namespace: "default",
},
}
if err := tc.syncBindingEviction(key); (err != nil) != tt.wantErr {
t.Errorf("NoExecuteTaintManager.syncBindingEviction() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wrb != nil {
gRb := &workv1alpha2.ResourceBinding{}
if err := tc.Get(context.Background(), types.NamespacedName{Name: tt.wrb.Name, Namespace: tt.wrb.Namespace}, gRb, &client.GetOptions{}); err != nil {
t.Fatalf("failed to get rb, error %v", err)
}
if !reflect.DeepEqual(tt.wrb.Spec, gRb.Spec) {
t.Errorf("ResourceBinding get %+v, want %+v", gRb.Spec, tt.wrb.Spec)
}
}
})
}
}
func TestNoExecuteTaintManager_syncClusterBindingEviction(t *testing.T) {
replica := int32(1)
tests := []struct {
name string
crb *workv1alpha2.ClusterResourceBinding
cluster *clusterv1alpha1.Cluster
wcrb *workv1alpha2.ClusterResourceBinding
wantErr bool
}{
{
name: "crb without tolerations",
crb: &workv1alpha2.ClusterResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ClusterResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-crb",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
},
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{
{
Key: "cluster.karmada.io/not-ready",
Effect: corev1.TaintEffectNoExecute,
},
},
},
},
wcrb: &workv1alpha2.ClusterResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ClusterResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-crb",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
GracefulEvictionTasks: []workv1alpha2.GracefulEvictionTask{
{
FromCluster: "test-cluster",
Replicas: &replica,
Reason: workv1alpha2.EvictionReasonTaintUntolerated,
Producer: workv1alpha2.EvictionProducerTaintManager,
},
},
},
},
wantErr: false,
},
{
name: "crb with tolerations",
crb: &workv1alpha2.ClusterResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ClusterResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-crb",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":30},{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
},
cluster: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
Spec: clusterv1alpha1.ClusterSpec{
Taints: []corev1.Taint{
{
Key: "cluster.karmada.io/not-ready",
Effect: corev1.TaintEffectNoExecute,
},
},
},
},
wcrb: &workv1alpha2.ClusterResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ClusterResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-crb",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
},
wantErr: false,
},
{
name: "crb not exist",
wantErr: false,
},
{
name: "cluster not exist",
crb: &workv1alpha2.ClusterResourceBinding{
TypeMeta: metav1.TypeMeta{
Kind: "ClusterResourceBinding",
APIVersion: "work.karmada.io/v1alpha2",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-crb",
Annotations: map[string]string{"policy.karmada.io/applied-placement": `{"clusterAffinity":{"clusterNames":["member1","member2"]},"clusterTolerations":[{"key":"cluster.karmada.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":30},{"key":"cluster.karmada.io/unreachable","operator":"Exists","effect":"NoExecute","tolerationSeconds":30}],"replicaScheduling":{"replicaSchedulingType":"Divided","replicaDivisionPreference":"Weighted","weightPreference":{"staticWeightList":[{"targetCluster":{"clusterNames":["member1"]},"weight":1},{"targetCluster":{"clusterNames":["member2"]},"weight":1}]}}}`},
},
Spec: workv1alpha2.ResourceBindingSpec{
Clusters: []workv1alpha2.TargetCluster{
{
Name: "test-cluster",
Replicas: 1,
},
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tc := newNoExecuteTaintManager()
if tt.crb != nil {
if err := tc.Create(context.Background(), tt.crb, &client.CreateOptions{}); err != nil {
t.Fatalf("failed to create crb: %v", err)
}
}
if tt.cluster != nil {
if err := tc.Create(context.Background(), tt.cluster, &client.CreateOptions{}); err != nil {
t.Fatalf("failed to create cluster: %v", err)
}
}
key := keys.FederatedKey{
Cluster: "test-cluster",
ClusterWideKey: keys.ClusterWideKey{
Kind: "ClusterResourceBinding",
Name: "test-crb",
},
}
if err := tc.syncClusterBindingEviction(key); (err != nil) != tt.wantErr {
t.Errorf("NoExecuteTaintManager.syncClusterBindingEviction() error = %v, wantErr %v", err, tt.wantErr)
}
if tt.wcrb != nil {
gCRB := &workv1alpha2.ClusterResourceBinding{}
if err := tc.Get(context.Background(), types.NamespacedName{Name: tt.wcrb.Name}, gCRB, &client.GetOptions{}); err != nil {
t.Fatalf("failed to get rb, error %v", err)
}
if !reflect.DeepEqual(tt.wcrb.Spec, gCRB.Spec) {
t.Errorf("ResourceBinding get %+v, want %+v", gCRB.Spec, tt.wcrb.Spec)
}
}
})
}
}