add ut for WorkloadRebalancer controller.
Signed-off-by: chaosi-zju <chaosi@zju.edu.cn>
This commit is contained in:
parent
4a0876cff5
commit
2b7d07ba1f
|
@ -0,0 +1,323 @@
|
||||||
|
/*
|
||||||
|
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 workloadrebalancer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||||
|
|
||||||
|
appsv1alpha1 "github.com/karmada-io/karmada/pkg/apis/apps/v1alpha1"
|
||||||
|
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/gclient"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/names"
|
||||||
|
"github.com/karmada-io/karmada/test/helper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
now = metav1.Now()
|
||||||
|
oneHourAgo = metav1.NewTime(time.Now().Add(-1 * time.Hour))
|
||||||
|
|
||||||
|
deploy1 = helper.NewDeployment("test-ns", "test-1")
|
||||||
|
binding1 = newResourceBinding(deploy1)
|
||||||
|
deploy1Obj = newObjectReference(deploy1)
|
||||||
|
|
||||||
|
// use deploy2 to mock a resource whose resource-binding not found.
|
||||||
|
deploy2 = helper.NewDeployment("test-ns", "test-2")
|
||||||
|
deploy2Obj = newObjectReference(deploy2)
|
||||||
|
|
||||||
|
deploy3 = helper.NewDeployment("test-ns", "test-3")
|
||||||
|
binding3 = newResourceBinding(deploy3)
|
||||||
|
deploy3Obj = newObjectReference(deploy3)
|
||||||
|
|
||||||
|
pendingRebalancer = &appsv1alpha1.WorkloadRebalancer{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "rebalancer-with-pending-workloads", CreationTimestamp: now},
|
||||||
|
Spec: appsv1alpha1.WorkloadRebalancerSpec{
|
||||||
|
// Put deploy2Obj before deploy1Obj to test whether the results of status are sorted.
|
||||||
|
Workloads: []appsv1alpha1.ObjectReference{deploy2Obj, deploy1Obj},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
succeedRebalancer = &appsv1alpha1.WorkloadRebalancer{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "rebalancer-with-succeed-workloads", CreationTimestamp: oneHourAgo},
|
||||||
|
Spec: appsv1alpha1.WorkloadRebalancerSpec{
|
||||||
|
Workloads: []appsv1alpha1.ObjectReference{deploy1Obj},
|
||||||
|
},
|
||||||
|
Status: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
Workload: deploy1Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceSuccessful,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
notFoundRebalancer = &appsv1alpha1.WorkloadRebalancer{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "rebalancer-with-workloads-whose-binding-not-found", CreationTimestamp: now},
|
||||||
|
Spec: appsv1alpha1.WorkloadRebalancerSpec{
|
||||||
|
Workloads: []appsv1alpha1.ObjectReference{deploy2Obj},
|
||||||
|
},
|
||||||
|
Status: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
Workload: deploy2Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceFailed,
|
||||||
|
Reason: appsv1alpha1.RebalanceObjectNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
failedRebalancer = &appsv1alpha1.WorkloadRebalancer{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "rebalancer-with-failed-workloads", CreationTimestamp: now},
|
||||||
|
Spec: appsv1alpha1.WorkloadRebalancerSpec{
|
||||||
|
Workloads: []appsv1alpha1.ObjectReference{deploy1Obj},
|
||||||
|
},
|
||||||
|
Status: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
// failed workload doesn't have a `Result` field and continue to retry.
|
||||||
|
Workload: deploy1Obj,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
modifiedRebalancer = &appsv1alpha1.WorkloadRebalancer{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "rebalancer-which-experienced-modification", CreationTimestamp: oneHourAgo},
|
||||||
|
Spec: appsv1alpha1.WorkloadRebalancerSpec{
|
||||||
|
Workloads: []appsv1alpha1.ObjectReference{deploy3Obj},
|
||||||
|
},
|
||||||
|
Status: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
Workload: deploy1Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceSuccessful,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Workload: deploy2Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceFailed,
|
||||||
|
Reason: appsv1alpha1.RebalanceObjectNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRebalancerController_Reconcile(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req controllerruntime.Request
|
||||||
|
existObjects []client.Object
|
||||||
|
existObjsWithStatus []client.Object
|
||||||
|
wantErr bool
|
||||||
|
wantStatus appsv1alpha1.WorkloadRebalancerStatus
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "reconcile pendingRebalancer",
|
||||||
|
req: controllerruntime.Request{
|
||||||
|
NamespacedName: types.NamespacedName{Name: pendingRebalancer.Name},
|
||||||
|
},
|
||||||
|
existObjects: []client.Object{deploy1, binding1, pendingRebalancer},
|
||||||
|
existObjsWithStatus: []client.Object{pendingRebalancer},
|
||||||
|
wantStatus: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
Workload: deploy1Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceSuccessful,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Workload: deploy2Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceFailed,
|
||||||
|
Reason: appsv1alpha1.RebalanceObjectNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reconcile succeedRebalancer",
|
||||||
|
req: controllerruntime.Request{
|
||||||
|
NamespacedName: types.NamespacedName{Name: succeedRebalancer.Name},
|
||||||
|
},
|
||||||
|
existObjects: []client.Object{deploy1, binding1, succeedRebalancer},
|
||||||
|
existObjsWithStatus: []client.Object{succeedRebalancer},
|
||||||
|
wantStatus: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
Workload: deploy1Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceSuccessful,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reconcile notFoundRebalancer",
|
||||||
|
req: controllerruntime.Request{
|
||||||
|
NamespacedName: types.NamespacedName{Name: notFoundRebalancer.Name},
|
||||||
|
},
|
||||||
|
existObjects: []client.Object{notFoundRebalancer},
|
||||||
|
existObjsWithStatus: []client.Object{notFoundRebalancer},
|
||||||
|
wantStatus: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
Workload: deploy2Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceFailed,
|
||||||
|
Reason: appsv1alpha1.RebalanceObjectNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reconcile failedRebalancer",
|
||||||
|
req: controllerruntime.Request{
|
||||||
|
NamespacedName: types.NamespacedName{Name: failedRebalancer.Name},
|
||||||
|
},
|
||||||
|
existObjects: []client.Object{deploy1, binding1, failedRebalancer},
|
||||||
|
existObjsWithStatus: []client.Object{failedRebalancer},
|
||||||
|
wantStatus: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
Workload: deploy1Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceSuccessful,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reconcile modifiedRebalancer",
|
||||||
|
req: controllerruntime.Request{
|
||||||
|
NamespacedName: types.NamespacedName{Name: modifiedRebalancer.Name},
|
||||||
|
},
|
||||||
|
existObjects: []client.Object{deploy1, deploy3, binding1, binding3, modifiedRebalancer},
|
||||||
|
existObjsWithStatus: []client.Object{modifiedRebalancer},
|
||||||
|
wantStatus: appsv1alpha1.WorkloadRebalancerStatus{
|
||||||
|
ObservedWorkloads: []appsv1alpha1.ObservedWorkload{
|
||||||
|
{
|
||||||
|
Workload: deploy1Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceSuccessful,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Workload: deploy3Obj,
|
||||||
|
Result: appsv1alpha1.RebalanceSuccessful,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &RebalancerController{
|
||||||
|
Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).
|
||||||
|
WithObjects(tt.existObjects...).
|
||||||
|
WithStatusSubresource(tt.existObjsWithStatus...).Build(),
|
||||||
|
}
|
||||||
|
_, err := c.Reconcile(context.TODO(), tt.req)
|
||||||
|
// 1. check whether it has error
|
||||||
|
if (err == nil && tt.wantErr) || (err != nil && !tt.wantErr) {
|
||||||
|
t.Fatalf("Reconcile() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
// 2. check final WorkloadRebalancer status
|
||||||
|
rebalancerGet := &appsv1alpha1.WorkloadRebalancer{}
|
||||||
|
if err := c.Client.Get(context.TODO(), tt.req.NamespacedName, rebalancerGet); err != nil {
|
||||||
|
t.Fatalf("get WorkloadRebalancer failed: %+v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(rebalancerGet.Status, tt.wantStatus) {
|
||||||
|
t.Fatalf("update WorkloadRebalancer failed, got: %+v, want: %+v", rebalancerGet.Status, tt.wantStatus)
|
||||||
|
}
|
||||||
|
// 3. check binding's rescheduleTriggeredAt
|
||||||
|
for _, item := range rebalancerGet.Status.ObservedWorkloads {
|
||||||
|
if item.Result != appsv1alpha1.RebalanceSuccessful {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bindingGet := &workv1alpha2.ResourceBinding{}
|
||||||
|
bindingName := names.GenerateBindingName(item.Workload.Kind, item.Workload.Name)
|
||||||
|
if err := c.Client.Get(context.TODO(), client.ObjectKey{Namespace: item.Workload.Namespace, Name: bindingName}, bindingGet); err != nil {
|
||||||
|
t.Fatalf("get bindding (%s) failed: %+v", bindingName, err)
|
||||||
|
}
|
||||||
|
if !bindingGet.Spec.RescheduleTriggeredAt.Equal(&rebalancerGet.CreationTimestamp) {
|
||||||
|
t.Fatalf("rescheduleTriggeredAt of binding got: %+v, want: %+v", bindingGet.Spec.RescheduleTriggeredAt, rebalancerGet.CreationTimestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRebalancerController_updateWorkloadRebalancerStatus(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
rebalancer *appsv1alpha1.WorkloadRebalancer
|
||||||
|
modifiedRebalancer *appsv1alpha1.WorkloadRebalancer
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "add newStatus to pendingRebalancer",
|
||||||
|
rebalancer: pendingRebalancer,
|
||||||
|
modifiedRebalancer: succeedRebalancer,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update status of failedRebalancer to newStatus",
|
||||||
|
rebalancer: failedRebalancer,
|
||||||
|
modifiedRebalancer: succeedRebalancer,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &RebalancerController{
|
||||||
|
Client: fake.NewClientBuilder().WithScheme(gclient.NewSchema()).
|
||||||
|
WithObjects(tt.rebalancer, tt.modifiedRebalancer).
|
||||||
|
WithStatusSubresource(tt.rebalancer, tt.modifiedRebalancer).Build(),
|
||||||
|
}
|
||||||
|
newStatus := tt.modifiedRebalancer.Status
|
||||||
|
err := c.updateWorkloadRebalancerStatus(context.TODO(), tt.rebalancer, &newStatus)
|
||||||
|
if (err == nil && tt.wantErr) || (err != nil && !tt.wantErr) {
|
||||||
|
t.Fatalf("updateWorkloadRebalancerStatus() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
rebalancerGet := &appsv1alpha1.WorkloadRebalancer{}
|
||||||
|
if err := c.Client.Get(context.TODO(), client.ObjectKey{Name: tt.modifiedRebalancer.Name}, rebalancerGet); err != nil {
|
||||||
|
t.Fatalf("get WorkloadRebalancer failed: %+v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(rebalancerGet.Status, newStatus) {
|
||||||
|
t.Fatalf("update WorkloadRebalancer failed, got: %+v, want: %+v", rebalancerGet.Status, tt.modifiedRebalancer.Status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResourceBinding(obj *appsv1.Deployment) *workv1alpha2.ResourceBinding {
|
||||||
|
return &workv1alpha2.ResourceBinding{
|
||||||
|
TypeMeta: metav1.TypeMeta{Kind: "work.karmada.io/v1alpha2", APIVersion: "ResourceBinding"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Namespace: obj.Namespace, Name: names.GenerateBindingName(obj.Kind, obj.Name)},
|
||||||
|
Spec: workv1alpha2.ResourceBindingSpec{RescheduleTriggeredAt: &oneHourAgo},
|
||||||
|
Status: workv1alpha2.ResourceBindingStatus{LastScheduledTime: &oneHourAgo},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newObjectReference(obj *appsv1.Deployment) appsv1alpha1.ObjectReference {
|
||||||
|
return appsv1alpha1.ObjectReference{
|
||||||
|
APIVersion: obj.APIVersion,
|
||||||
|
Kind: obj.Kind,
|
||||||
|
Name: obj.Name,
|
||||||
|
Namespace: obj.Namespace,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue