237 lines
9.6 KiB
Go
237 lines
9.6 KiB
Go
/*
|
|
Copyright 2024 The Kubernetes 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 provider
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
|
|
apiv1 "k8s.io/api/core/v1"
|
|
resourceapi "k8s.io/api/resource/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
drasnapshot "k8s.io/autoscaler/cluster-autoscaler/simulator/dynamicresources/snapshot"
|
|
"k8s.io/autoscaler/cluster-autoscaler/utils/test"
|
|
"k8s.io/client-go/informers"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
)
|
|
|
|
var (
|
|
claim1 = &resourceapi.ResourceClaim{ObjectMeta: metav1.ObjectMeta{Name: "claim-1", UID: "claim-1"}}
|
|
claim2 = &resourceapi.ResourceClaim{ObjectMeta: metav1.ObjectMeta{Name: "claim-2", UID: "claim-2"}}
|
|
claim3 = &resourceapi.ResourceClaim{ObjectMeta: metav1.ObjectMeta{Name: "claim-3", UID: "claim-3"}}
|
|
|
|
n1Name = "n1"
|
|
n2Name = "n2"
|
|
trueValue = true
|
|
|
|
localSlice1 = &resourceapi.ResourceSlice{ObjectMeta: metav1.ObjectMeta{Name: "local-slice-1", UID: "local-slice-1"}, Spec: resourceapi.ResourceSliceSpec{NodeName: &n1Name}}
|
|
localSlice2 = &resourceapi.ResourceSlice{ObjectMeta: metav1.ObjectMeta{Name: "local-slice-2", UID: "local-slice-2"}, Spec: resourceapi.ResourceSliceSpec{NodeName: &n1Name}}
|
|
localSlice3 = &resourceapi.ResourceSlice{ObjectMeta: metav1.ObjectMeta{Name: "local-slice-3", UID: "local-slice-3"}, Spec: resourceapi.ResourceSliceSpec{NodeName: &n2Name}}
|
|
localSlice4 = &resourceapi.ResourceSlice{ObjectMeta: metav1.ObjectMeta{Name: "local-slice-4", UID: "local-slice-4"}, Spec: resourceapi.ResourceSliceSpec{NodeName: &n2Name}}
|
|
globalSlice1 = &resourceapi.ResourceSlice{ObjectMeta: metav1.ObjectMeta{Name: "global-slice-1", UID: "global-slice-1"}, Spec: resourceapi.ResourceSliceSpec{AllNodes: &trueValue}}
|
|
globalSlice2 = &resourceapi.ResourceSlice{ObjectMeta: metav1.ObjectMeta{Name: "global-slice-2", UID: "global-slice-2"}, Spec: resourceapi.ResourceSliceSpec{AllNodes: &trueValue}}
|
|
globalSlice3 = &resourceapi.ResourceSlice{ObjectMeta: metav1.ObjectMeta{Name: "global-slice-3", UID: "global-slice-3"}, Spec: resourceapi.ResourceSliceSpec{NodeSelector: &apiv1.NodeSelector{}}}
|
|
|
|
class1 = &resourceapi.DeviceClass{ObjectMeta: metav1.ObjectMeta{Name: "class-1", UID: "class-1"}}
|
|
class2 = &resourceapi.DeviceClass{ObjectMeta: metav1.ObjectMeta{Name: "class-2", UID: "class-2"}}
|
|
class3 = &resourceapi.DeviceClass{ObjectMeta: metav1.ObjectMeta{Name: "class-3", UID: "class-3"}}
|
|
)
|
|
|
|
func TestProviderSnapshot(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
testName string
|
|
claims []*resourceapi.ResourceClaim
|
|
triggerClaimsError bool
|
|
slices []*resourceapi.ResourceSlice
|
|
triggerSlicesError bool
|
|
classes []*resourceapi.DeviceClass
|
|
triggerClassesError bool
|
|
wantSnapshot *drasnapshot.Snapshot
|
|
wantErr error
|
|
}{
|
|
{
|
|
testName: "claim lister error results in an error",
|
|
triggerClaimsError: true,
|
|
wantErr: cmpopts.AnyError,
|
|
},
|
|
{
|
|
testName: "slices lister error results in an error",
|
|
triggerSlicesError: true,
|
|
wantErr: cmpopts.AnyError,
|
|
},
|
|
{
|
|
testName: "classes lister error results in an error",
|
|
triggerClassesError: true,
|
|
wantErr: cmpopts.AnyError,
|
|
},
|
|
{
|
|
testName: "claims are correctly snapshot by id",
|
|
claims: []*resourceapi.ResourceClaim{claim1, claim2, claim3},
|
|
wantSnapshot: drasnapshot.NewSnapshot(
|
|
map[drasnapshot.ResourceClaimId]*resourceapi.ResourceClaim{
|
|
drasnapshot.GetClaimId(claim1): claim1,
|
|
drasnapshot.GetClaimId(claim2): claim2,
|
|
drasnapshot.GetClaimId(claim3): claim3,
|
|
}, nil, nil, nil),
|
|
},
|
|
{
|
|
testName: "slices are correctly divided and snapshot",
|
|
slices: []*resourceapi.ResourceSlice{localSlice1, localSlice2, localSlice3, localSlice4, globalSlice1, globalSlice2, globalSlice3},
|
|
wantSnapshot: drasnapshot.NewSnapshot(nil,
|
|
map[string][]*resourceapi.ResourceSlice{
|
|
"n1": {localSlice1, localSlice2},
|
|
"n2": {localSlice3, localSlice4},
|
|
},
|
|
[]*resourceapi.ResourceSlice{globalSlice1, globalSlice2, globalSlice3}, nil),
|
|
},
|
|
{
|
|
testName: "classes are correctly snapshot by name",
|
|
classes: []*resourceapi.DeviceClass{class1, class2, class3},
|
|
wantSnapshot: drasnapshot.NewSnapshot(nil, nil, nil,
|
|
map[string]*resourceapi.DeviceClass{"class-1": class1, "class-2": class2, "class-3": class3}),
|
|
},
|
|
{
|
|
testName: "everything is correctly snapshot together",
|
|
claims: []*resourceapi.ResourceClaim{claim1, claim2, claim3},
|
|
slices: []*resourceapi.ResourceSlice{localSlice1, localSlice2, localSlice3, localSlice4, globalSlice1, globalSlice2, globalSlice3},
|
|
classes: []*resourceapi.DeviceClass{class1, class2, class3},
|
|
wantSnapshot: drasnapshot.NewSnapshot(
|
|
map[drasnapshot.ResourceClaimId]*resourceapi.ResourceClaim{
|
|
drasnapshot.GetClaimId(claim1): claim1,
|
|
drasnapshot.GetClaimId(claim2): claim2,
|
|
drasnapshot.GetClaimId(claim3): claim3,
|
|
},
|
|
map[string][]*resourceapi.ResourceSlice{
|
|
"n1": {localSlice1, localSlice2},
|
|
"n2": {localSlice3, localSlice4},
|
|
},
|
|
[]*resourceapi.ResourceSlice{globalSlice1, globalSlice2, globalSlice3},
|
|
map[string]*resourceapi.DeviceClass{"class-1": class1, "class-2": class2, "class-3": class3}),
|
|
},
|
|
} {
|
|
t.Run(tc.testName, func(t *testing.T) {
|
|
claimLister := &fakeLister[*resourceapi.ResourceClaim]{objects: tc.claims, triggerErr: tc.triggerClaimsError}
|
|
sliceLister := &fakeLister[*resourceapi.ResourceSlice]{objects: tc.slices, triggerErr: tc.triggerSlicesError}
|
|
classLister := &fakeLister[*resourceapi.DeviceClass]{objects: tc.classes, triggerErr: tc.triggerClassesError}
|
|
provider := NewProvider(claimLister, sliceLister, classLister)
|
|
snapshot, err := provider.Snapshot()
|
|
if diff := cmp.Diff(tc.wantErr, err, cmpopts.EquateErrors()); diff != "" {
|
|
t.Fatalf("Provider.Snapshot(): unexpected error (-want +got): %s", diff)
|
|
}
|
|
if diff := cmp.Diff(tc.wantSnapshot, snapshot, drasnapshot.SnapshotFlattenedComparer()); diff != "" {
|
|
t.Fatalf("Provider.Snapshot(): snapshot differs from expected (-want +got): %s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNewProviderFromInformers verifies that the interface translation listers created in NewProviderFromInformers correctly return
|
|
// all objects in the cluster.
|
|
func TestNewProviderFromInformers(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
testName string
|
|
claims []*resourceapi.ResourceClaim
|
|
slices []*resourceapi.ResourceSlice
|
|
classes []*resourceapi.DeviceClass
|
|
}{
|
|
{
|
|
testName: "no objects in informers",
|
|
},
|
|
{
|
|
testName: "ResourceClaims present in informers",
|
|
claims: []*resourceapi.ResourceClaim{claim1, claim2, claim3},
|
|
},
|
|
{
|
|
testName: "ResourceSlices present in informers",
|
|
slices: []*resourceapi.ResourceSlice{localSlice1, localSlice2, localSlice3},
|
|
},
|
|
{
|
|
testName: "DeviceClasses present in informers",
|
|
classes: []*resourceapi.DeviceClass{class1, class2, class3},
|
|
},
|
|
{
|
|
testName: "all objects present in informers together",
|
|
claims: []*resourceapi.ResourceClaim{claim1, claim2, claim3},
|
|
slices: []*resourceapi.ResourceSlice{localSlice1, localSlice2, localSlice3},
|
|
classes: []*resourceapi.DeviceClass{class1, class2, class3},
|
|
},
|
|
} {
|
|
t.Run(tc.testName, func(t *testing.T) {
|
|
var objects []runtime.Object
|
|
for _, claim := range tc.claims {
|
|
objects = append(objects, claim)
|
|
}
|
|
for _, slice := range tc.slices {
|
|
objects = append(objects, slice)
|
|
}
|
|
for _, class := range tc.classes {
|
|
objects = append(objects, class)
|
|
}
|
|
client := fake.NewSimpleClientset(objects...)
|
|
informerFactory := informers.NewSharedInformerFactory(client, 0)
|
|
provider := NewProviderFromInformers(informerFactory)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
informerFactory.Start(ctx.Done())
|
|
informerFactory.WaitForCacheSync(ctx.Done())
|
|
|
|
allClaims, err := provider.resourceClaims.ListAll()
|
|
if err != nil {
|
|
t.Fatalf("provider.resourceClaims.ListAll(): got unexpected error %v", err)
|
|
}
|
|
if diff := cmp.Diff(tc.claims, allClaims, test.IgnoreObjectOrder[*resourceapi.ResourceClaim]()); diff != "" {
|
|
t.Errorf("provider.resourceClaims.ListAll(): result differs from expected (-want +got): %s", diff)
|
|
}
|
|
|
|
allSlices, err := provider.resourceSlices.ListAll()
|
|
if err != nil {
|
|
t.Fatalf("provider.resourceSlices.ListAll(): got unexpected error %v", err)
|
|
}
|
|
if diff := cmp.Diff(tc.slices, allSlices, test.IgnoreObjectOrder[*resourceapi.ResourceSlice]()); diff != "" {
|
|
t.Errorf("provider.resourceSlices.ListAll(): result differs from expected (-want +got): %s", diff)
|
|
}
|
|
|
|
allClasses, err := provider.deviceClasses.ListAll()
|
|
if err != nil {
|
|
t.Fatalf("provider.deviceClasses.ListAll(): got unexpected error %v", err)
|
|
}
|
|
if diff := cmp.Diff(tc.classes, allClasses, test.IgnoreObjectOrder[*resourceapi.DeviceClass]()); diff != "" {
|
|
t.Errorf("provider.deviceClasses.ListAll(): result differs from expected (-want +got): %s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type fakeLister[T any] struct {
|
|
objects []T
|
|
triggerErr bool
|
|
}
|
|
|
|
func (l *fakeLister[T]) ListAll() ([]T, error) {
|
|
var err error
|
|
if l.triggerErr {
|
|
err = fmt.Errorf("fake test error")
|
|
}
|
|
return l.objects, err
|
|
}
|