Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tests for pkg/descheduler/descheduler.go #5653

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
320 changes: 320 additions & 0 deletions pkg/descheduler/descheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ package descheduler

import (
"context"
"errors"
"fmt"
"reflect"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"google.golang.org/grpc"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
Expand All @@ -39,10 +45,195 @@ import (
estimatorservice "github.com/karmada-io/karmada/pkg/estimator/service"
fakekarmadaclient "github.com/karmada-io/karmada/pkg/generated/clientset/versioned/fake"
informerfactory "github.com/karmada-io/karmada/pkg/generated/informers/externalversions"
worklister "github.com/karmada-io/karmada/pkg/generated/listers/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/util"
"github.com/karmada-io/karmada/pkg/util/helper"
)

func TestRecordDescheduleResultEventForResourceBinding(t *testing.T) {
tests := []struct {
name string
rb *workv1alpha2.ResourceBinding
message string
err error
expectedEvents []string
}{
{
name: "Nil ResourceBinding",
rb: nil,
message: "Test message",
err: nil,
expectedEvents: []string{},
},
{
name: "Successful descheduling",
rb: &workv1alpha2.ResourceBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "test-binding",
Namespace: "test-namespace",
},
Spec: workv1alpha2.ResourceBindingSpec{
Resource: workv1alpha2.ObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "test-deployment",
Namespace: "test-namespace",
UID: types.UID("test-uid"),
},
},
},
message: "Descheduling succeeded",
err: nil,
expectedEvents: []string{
"Normal DescheduleBindingSucceed Descheduling succeeded",
"Normal DescheduleBindingSucceed Descheduling succeeded",
},
},
{
name: "Failed descheduling",
rb: &workv1alpha2.ResourceBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "test-binding",
Namespace: "test-namespace",
},
Spec: workv1alpha2.ResourceBindingSpec{
Resource: workv1alpha2.ObjectReference{
APIVersion: "apps/v1",
Kind: "Deployment",
Name: "test-deployment",
Namespace: "test-namespace",
UID: types.UID("test-uid"),
},
},
},
message: "Descheduling failed",
err: errors.New("descheduling error"),
expectedEvents: []string{
"Warning DescheduleBindingFailed descheduling error",
"Warning DescheduleBindingFailed descheduling error",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeRecorder := record.NewFakeRecorder(10)
d := &Descheduler{
eventRecorder: fakeRecorder,
}

d.recordDescheduleResultEventForResourceBinding(tt.rb, tt.message, tt.err)

close(fakeRecorder.Events)
actualEvents := []string{}
for event := range fakeRecorder.Events {
actualEvents = append(actualEvents, event)
}

assert.Equal(t, tt.expectedEvents, actualEvents, "Recorded events do not match expected events")
})
}
}

func TestUpdateCluster(t *testing.T) {
tests := []struct {
name string
newObj interface{}
expectedAdd bool
}{
{
name: "Valid cluster update",
newObj: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
},
expectedAdd: true,
},
{
name: "Invalid object type",
newObj: &corev1.Pod{},
expectedAdd: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockWorker := &mockAsyncWorker{}
d := &Descheduler{
schedulerEstimatorWorker: mockWorker,
}

if tt.expectedAdd {
mockWorker.On("Add", mock.AnythingOfType("string")).Return()
}

d.updateCluster(nil, tt.newObj)

if tt.expectedAdd {
mockWorker.AssertCalled(t, "Add", "test-cluster")
} else {
mockWorker.AssertNotCalled(t, "Add", mock.Anything)
}
})
}
}

func TestDeleteCluster(t *testing.T) {
tests := []struct {
name string
obj interface{}
expectedAdd bool
}{
{
name: "Delete Cluster object",
obj: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
},
expectedAdd: true,
},
{
name: "Delete DeletedFinalStateUnknown object",
obj: cache.DeletedFinalStateUnknown{
Obj: &clusterv1alpha1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-cluster",
},
},
},
expectedAdd: true,
},
{
name: "Invalid object type",
obj: &corev1.Pod{},
expectedAdd: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockWorker := &mockAsyncWorker{}
d := &Descheduler{
schedulerEstimatorWorker: mockWorker,
}

if tt.expectedAdd {
mockWorker.On("Add", mock.AnythingOfType("string")).Return()
}

d.deleteCluster(tt.obj)

if tt.expectedAdd {
mockWorker.AssertCalled(t, "Add", "test-cluster")
} else {
mockWorker.AssertNotCalled(t, "Add", mock.Anything)
}
})
}
}

func buildBinding(name, ns string, target, status []workv1alpha2.TargetCluster) (*workv1alpha2.ResourceBinding, error) {
bindingStatus := workv1alpha2.ResourceBindingStatus{}
for _, cluster := range status {
Expand Down Expand Up @@ -630,3 +821,132 @@ func TestDescheduler_worker(t *testing.T) {
})
}
}

func TestDescheduler_workerErrors(t *testing.T) {
tests := []struct {
name string
key interface{}
setupMocks func(*Descheduler)
expectedError string
}{
{
name: "Invalid key type",
key: 123,
setupMocks: func(_ *Descheduler) {},
expectedError: "failed to deschedule as invalid key: 123",
},
{
name: "Invalid resource key format",
key: "invalid/key/format",
setupMocks: func(_ *Descheduler) {},
expectedError: "invalid resource key: invalid/key/format",
},
{
name: "ResourceBinding not found",
key: "default/non-existent-binding",
setupMocks: func(d *Descheduler) {
d.bindingLister = &mockBindingLister{
getErr: apierrors.NewNotFound(schema.GroupResource{Resource: "resourcebindings"}, "non-existent-binding"),
}
},
expectedError: "",
},
{
name: "Error getting ResourceBinding",
key: "default/error-binding",
setupMocks: func(d *Descheduler) {
d.bindingLister = &mockBindingLister{
getErr: fmt.Errorf("internal error"),
}
},
expectedError: "get ResourceBinding(default/error-binding) error: internal error",
},
{
name: "ResourceBinding being deleted",
key: "default/deleted-binding",
setupMocks: func(d *Descheduler) {
d.bindingLister = &mockBindingLister{
binding: &workv1alpha2.ResourceBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "deleted-binding",
Namespace: "default",
DeletionTimestamp: &metav1.Time{Time: time.Now()},
},
},
}
},
expectedError: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d := &Descheduler{}
tt.setupMocks(d)

err := d.worker(tt.key)

if tt.expectedError == "" {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, tt.expectedError)
}
})
}
}

// Mock Implementations

type mockAsyncWorker struct {
mock.Mock
}

func (m *mockAsyncWorker) Add(item interface{}) {
m.Called(item)
}

func (m *mockAsyncWorker) AddAfter(item interface{}, duration time.Duration) {
m.Called(item, duration)
}

func (m *mockAsyncWorker) Run(_ int, _ <-chan struct{}) {}

func (m *mockAsyncWorker) Enqueue(obj interface{}) {
m.Called(obj)
}

func (m *mockAsyncWorker) EnqueueAfter(obj interface{}, duration time.Duration) {
m.Called(obj, duration)
}

type mockBindingLister struct {
binding *workv1alpha2.ResourceBinding
getErr error
}

func (m *mockBindingLister) List(_ labels.Selector) (ret []*workv1alpha2.ResourceBinding, err error) {
return nil, nil
}

func (m *mockBindingLister) ResourceBindings(_ string) worklister.ResourceBindingNamespaceLister {
return &mockBindingNamespaceLister{
binding: m.binding,
getErr: m.getErr,
}
}

type mockBindingNamespaceLister struct {
binding *workv1alpha2.ResourceBinding
getErr error
}

func (m *mockBindingNamespaceLister) List(_ labels.Selector) (ret []*workv1alpha2.ResourceBinding, err error) {
return nil, nil
}

func (m *mockBindingNamespaceLister) Get(_ string) (*workv1alpha2.ResourceBinding, error) {
if m.getErr != nil {
return nil, m.getErr
}
return m.binding, nil
}