kubernetes/pkg/scheduler/framework/plugins/dynamicresources/structuredparameters_test.go
Patrick Ohly b51d68bb87 DRA: bump API v1alpha2 -> v1alpha3
This is in preparation for revamping the resource.k8s.io completely. Because
there will be no support for transitioning from v1alpha2 to v1alpha3, the
roundtrip test data for that API in 1.29 and 1.30 gets removed.

Repeating the version in the import name of the API packages is not really
required. It was done for a while to support simpler grepping for usage of
alpha APIs, but there are better ways for that now. So during this transition,
"resourceapi" gets used instead of "resourcev1alpha3" and the version gets
dropped from informer and lister imports. The advantage is that the next bump
to v1beta1 will affect fewer source code lines.

Only source code where the version really matters (like API registration)
retains the versioned import.
2024-07-21 17:28:13 +02:00

1258 lines
35 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 dynamicresources
import (
"errors"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
resourceapi "k8s.io/api/resource/v1alpha3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
namedresourcesmodel "k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources"
"k8s.io/kubernetes/test/utils/ktesting"
"k8s.io/utils/ptr"
)
func TestModel(t *testing.T) {
testcases := map[string]struct {
slices resourceSliceLister
claims assumeCacheLister
inFlight map[types.UID]resourceapi.ResourceClaimStatus
wantResources resources
wantErr bool
}{
"empty": {},
"slice-list-error": {
slices: sliceError("slice list error"),
wantErr: true,
},
"unknown-model": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node",
DriverName: "driver",
ResourceModel: resourceapi.ResourceModel{ /* empty! */ },
},
},
// Not an error. It is safe to ignore unknown resources until a claim requests them.
// The unknown model in that claim then triggers an error for that claim.
wantResources: resources{"node": map[string]ResourceModels{
"driver": {
NamedResources: namedresourcesmodel.Model{},
},
}},
},
"one-instance": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node",
DriverName: "driver",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{
Name: "one",
Attributes: []resourceapi.NamedResourcesAttribute{{
Name: "size",
NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{
IntValue: ptr.To(int64(1)),
},
}},
}},
},
},
},
},
wantResources: resources{"node": map[string]ResourceModels{
"driver": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{
Name: "one",
Attributes: []resourceapi.NamedResourcesAttribute{{
Name: "size",
NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{
IntValue: ptr.To(int64(1)),
},
}},
},
},
},
},
},
}},
},
"two-instances": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node",
DriverName: "driver",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
},
wantResources: resources{"node": map[string]ResourceModels{
"driver": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
}},
},
"two-nodes": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
},
wantResources: resources{
"node-a": map[string]ResourceModels{
"driver": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
},
"node-b": map[string]ResourceModels{
"driver": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
},
},
},
"two-nodes-two-drivers": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
},
wantResources: resources{
"node-a": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
},
"node-b": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
},
},
},
"in-use-simple": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
},
claims: claimList{
&resourceapi.ResourceClaim{
/* not allocated */
},
&resourceapi.ResourceClaim{
Status: resourceapi.ResourceClaimStatus{
DriverName: "driver-a",
Allocation: &resourceapi.AllocationResult{
ResourceHandles: []resourceapi.ResourceHandle{{
// Claims not allocated via structured parameters can be ignored.
}},
},
},
},
&resourceapi.ResourceClaim{
Status: resourceapi.ResourceClaimStatus{
DriverName: "driver-a",
Allocation: &resourceapi.AllocationResult{
ResourceHandles: []resourceapi.ResourceHandle{{
DriverName: "driver-a",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node-b",
// Unknown allocations can be ignored.
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{},
}},
},
}},
},
},
},
&resourceapi.ResourceClaim{
Status: resourceapi.ResourceClaimStatus{
DriverName: "driver-a",
Allocation: &resourceapi.AllocationResult{
ResourceHandles: []resourceapi.ResourceHandle{{
DriverName: "driver-a",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node-a",
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "two",
},
},
}},
},
}},
},
},
},
},
wantResources: resources{
"node-a": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Allocated: true,
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
},
"node-b": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
},
},
},
"in-use-meta-driver": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
},
claims: claimList{
&resourceapi.ResourceClaim{
Status: resourceapi.ResourceClaimStatus{
DriverName: "meta-driver",
Allocation: &resourceapi.AllocationResult{
ResourceHandles: []resourceapi.ResourceHandle{{
DriverName: "driver-b",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node-b",
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "X",
},
},
}},
},
}},
},
},
},
},
wantResources: resources{
"node-a": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
},
"node-b": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Allocated: true,
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
},
},
},
"in-use-many-results": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
},
claims: claimList{
&resourceapi.ResourceClaim{
Status: resourceapi.ResourceClaimStatus{
DriverName: "driver-a",
Allocation: &resourceapi.AllocationResult{
ResourceHandles: []resourceapi.ResourceHandle{{
DriverName: "driver-a",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node-a",
Results: []resourceapi.DriverAllocationResult{
{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "one",
},
},
},
{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "two",
},
},
},
},
},
}},
},
},
},
},
wantResources: resources{
"node-a": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Allocated: true,
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Allocated: true,
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
},
"node-b": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
},
},
},
"in-use-many-handles": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-a",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-a",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
&resourceapi.ResourceSlice{
NodeName: "node-b",
DriverName: "driver-b",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
},
},
},
},
claims: claimList{
&resourceapi.ResourceClaim{
Status: resourceapi.ResourceClaimStatus{
DriverName: "meta-driver",
Allocation: &resourceapi.AllocationResult{
ResourceHandles: []resourceapi.ResourceHandle{
{
DriverName: "driver-a",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node-b",
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "X",
},
},
}},
},
},
{
DriverName: "driver-b",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node-b",
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "X",
},
},
}},
},
},
},
},
},
},
},
wantResources: resources{
"node-a": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
},
},
},
},
},
"node-b": map[string]ResourceModels{
"driver-a": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Allocated: true,
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
"driver-b": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Allocated: true,
Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
},
{
Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
},
},
},
},
},
},
},
"orphaned-allocations": {
claims: claimList{
&resourceapi.ResourceClaim{
Status: resourceapi.ResourceClaimStatus{
DriverName: "meta-driver",
Allocation: &resourceapi.AllocationResult{
ResourceHandles: []resourceapi.ResourceHandle{
{
DriverName: "driver-a",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node-b",
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "X",
},
},
}},
},
},
{
DriverName: "driver-b",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node-b",
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "X",
},
},
}},
},
},
},
},
},
},
},
wantResources: resources{
"node-b": map[string]ResourceModels{},
},
},
"in-flight": {
slices: sliceList{
&resourceapi.ResourceSlice{
NodeName: "node",
DriverName: "driver",
ResourceModel: resourceapi.ResourceModel{
NamedResources: &resourceapi.NamedResourcesResources{
Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}},
},
},
},
},
claims: claimList{
&resourceapi.ResourceClaim{
ObjectMeta: metav1.ObjectMeta{
UID: "abc",
},
// Allocation not recorded yet.
},
},
inFlight: map[types.UID]resourceapi.ResourceClaimStatus{
"abc": {
DriverName: "driver",
Allocation: &resourceapi.AllocationResult{
ResourceHandles: []resourceapi.ResourceHandle{{
DriverName: "driver",
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: "node",
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: "one",
},
},
}},
},
}},
},
},
},
wantResources: resources{"node": map[string]ResourceModels{
"driver": {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{
{
Allocated: true,
Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
},
},
},
},
}},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
tCtx := ktesting.Init(t)
var inFlightAllocations sync.Map
for uid, claimStatus := range tc.inFlight {
inFlightAllocations.Store(uid, &resourceapi.ResourceClaim{Status: claimStatus})
}
slices := tc.slices
if slices == nil {
slices = sliceList{}
}
claims := tc.claims
if claims == nil {
claims = claimList{}
}
actualResources, actualErr := newResourceModel(tCtx.Logger(), slices, claims, &inFlightAllocations)
if actualErr != nil {
if !tc.wantErr {
tCtx.Fatalf("unexpected error: %v", actualErr)
}
return
}
if tc.wantErr {
tCtx.Fatalf("did not get expected error")
}
expectResources := tc.wantResources
if expectResources == nil {
expectResources = resources{}
}
require.Equal(tCtx, expectResources, actualResources)
})
}
}
type sliceList []*resourceapi.ResourceSlice
func (l sliceList) List(selector labels.Selector) ([]*resourceapi.ResourceSlice, error) {
return l, nil
}
type sliceError string
func (l sliceError) List(selector labels.Selector) ([]*resourceapi.ResourceSlice, error) {
return nil, errors.New(string(l))
}
type claimList []any
func (l claimList) List(indexObj any) []any {
return l
}
func TestController(t *testing.T) {
driver1 := "driver-1"
class1 := &resourceapi.ResourceClass{
DriverName: driver1,
}
classParametersEmpty := &resourceapi.ResourceClassParameters{}
classParametersAny := &resourceapi.ResourceClassParameters{
Filters: []resourceapi.ResourceFilter{{
DriverName: driver1,
ResourceFilterModel: resourceapi.ResourceFilterModel{
NamedResources: &resourceapi.NamedResourcesFilter{
Selector: "true",
},
},
}},
}
claimParametersEmpty := &resourceapi.ResourceClaimParameters{}
claimParametersAny := &resourceapi.ResourceClaimParameters{
DriverRequests: []resourceapi.DriverRequests{{
DriverName: driver1,
}},
}
claimParametersOne := &resourceapi.ResourceClaimParameters{
DriverRequests: []resourceapi.DriverRequests{{
DriverName: driver1,
Requests: []resourceapi.ResourceRequest{{
ResourceRequestModel: resourceapi.ResourceRequestModel{
NamedResources: &resourceapi.NamedResourcesRequest{
Selector: "true",
},
},
}},
}},
}
claimParametersBroken := &resourceapi.ResourceClaimParameters{
DriverRequests: []resourceapi.DriverRequests{{
DriverName: driver1,
Requests: []resourceapi.ResourceRequest{{
ResourceRequestModel: resourceapi.ResourceRequestModel{
NamedResources: &resourceapi.NamedResourcesRequest{
Selector: `attributes.bool["no-such-attribute"]`,
},
},
}},
}},
}
instance1 := "instance-1"
node1 := "node-1"
node1Selector := &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{{
MatchExpressions: []v1.NodeSelectorRequirement{{
Key: "kubernetes.io/hostname",
Operator: v1.NodeSelectorOpIn,
Values: []string{node1},
}},
}},
}
node1Resources := resources{node1: map[string]ResourceModels{
driver1: {
NamedResources: namedresourcesmodel.Model{
Instances: []namedresourcesmodel.InstanceAllocation{{
Instance: &resourceapi.NamedResourcesInstance{
Name: instance1,
},
}},
},
},
}}
node1Allocation := &resourceapi.AllocationResult{
AvailableOnNodes: node1Selector,
}
instance1Allocation := &resourceapi.AllocationResult{
AvailableOnNodes: node1Selector,
ResourceHandles: []resourceapi.ResourceHandle{{
DriverName: driver1,
StructuredData: &resourceapi.StructuredResourceHandle{
NodeName: node1,
Results: []resourceapi.DriverAllocationResult{{
AllocationResultModel: resourceapi.AllocationResultModel{
NamedResources: &resourceapi.NamedResourcesAllocationResult{
Name: instance1,
},
},
}},
},
}},
}
type nodeResult struct {
isSuitable bool
suitableErr string
driverName string
allocation *resourceapi.AllocationResult
allocateErr string
}
type nodeResults map[string]nodeResult
testcases := map[string]struct {
resources resources
class *resourceapi.ResourceClass
classParameters *resourceapi.ResourceClassParameters
claimParameters *resourceapi.ResourceClaimParameters
expectCreateErr bool
expectNodeResults nodeResults
}{
"empty": {
class: class1,
classParameters: classParametersEmpty,
claimParameters: claimParametersEmpty,
expectNodeResults: nodeResults{
node1: {isSuitable: true, driverName: driver1, allocation: node1Allocation},
},
},
"any": {
class: class1,
classParameters: classParametersEmpty,
claimParameters: claimParametersAny,
expectNodeResults: nodeResults{
node1: {isSuitable: true, driverName: driver1, allocation: node1Allocation},
},
},
"missing-model": {
class: class1,
classParameters: classParametersEmpty,
claimParameters: &resourceapi.ResourceClaimParameters{
DriverRequests: []resourceapi.DriverRequests{{
Requests: []resourceapi.ResourceRequest{{ /* empty model */ }},
}},
},
expectCreateErr: true,
},
"no-resources": {
class: class1,
classParameters: classParametersEmpty,
claimParameters: claimParametersOne,
expectNodeResults: nodeResults{
node1: {isSuitable: false, allocateErr: "allocating via named resources structured model: insufficient resources"},
},
},
"have-resources": {
resources: node1Resources,
class: class1,
classParameters: classParametersEmpty,
claimParameters: claimParametersOne,
expectNodeResults: nodeResults{
node1: {isSuitable: true, driverName: driver1, allocation: instance1Allocation},
},
},
"broken-cel": {
resources: node1Resources,
class: class1,
classParameters: classParametersEmpty,
claimParameters: claimParametersBroken,
expectNodeResults: nodeResults{
node1: {suitableErr: `checking node "node-1" and resources of driver "driver-1": evaluate request CEL expression: no such key: no-such-attribute`},
},
},
"class-filter": {
resources: node1Resources,
class: class1,
classParameters: classParametersAny,
claimParameters: claimParametersOne,
expectNodeResults: nodeResults{
node1: {isSuitable: true, driverName: driver1, allocation: instance1Allocation},
},
},
"vendor-parameters": {
resources: node1Resources,
class: class1,
classParameters: func() *resourceapi.ResourceClassParameters {
parameters := classParametersAny.DeepCopy()
parameters.VendorParameters = []resourceapi.VendorParameters{{
DriverName: driver1,
Parameters: runtime.RawExtension{Raw: []byte("class-parameters")},
}}
return parameters
}(),
claimParameters: func() *resourceapi.ResourceClaimParameters {
parameters := claimParametersOne.DeepCopy()
parameters.DriverRequests[0].VendorParameters = runtime.RawExtension{Raw: []byte("claim-parameters")}
parameters.DriverRequests[0].Requests[0].VendorParameters = runtime.RawExtension{Raw: []byte("request-parameters")}
return parameters
}(),
expectNodeResults: nodeResults{
node1: {isSuitable: true, driverName: driver1,
allocation: func() *resourceapi.AllocationResult {
allocation := instance1Allocation.DeepCopy()
allocation.ResourceHandles[0].StructuredData.VendorClassParameters = runtime.RawExtension{Raw: []byte("class-parameters")}
allocation.ResourceHandles[0].StructuredData.VendorClaimParameters = runtime.RawExtension{Raw: []byte("claim-parameters")}
allocation.ResourceHandles[0].StructuredData.Results[0].VendorRequestParameters = runtime.RawExtension{Raw: []byte("request-parameters")}
return allocation
}(),
},
},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
tCtx := ktesting.Init(t)
controller, err := newClaimController(tCtx.Logger(), tc.class, tc.classParameters, tc.claimParameters)
if err != nil {
if !tc.expectCreateErr {
tCtx.Fatalf("unexpected error: %v", err)
}
return
}
if tc.expectCreateErr {
tCtx.Fatalf("did not get expected error")
}
for nodeName, expect := range tc.expectNodeResults {
t.Run(nodeName, func(t *testing.T) {
tCtx := ktesting.Init(t)
isSuitable, err := controller.nodeIsSuitable(tCtx, nodeName, tc.resources)
if err != nil {
if expect.suitableErr == "" {
tCtx.Fatalf("unexpected nodeIsSuitable error: %v", err)
}
require.Equal(tCtx, expect.suitableErr, err.Error())
return
}
if expect.suitableErr != "" {
tCtx.Fatalf("did not get expected nodeIsSuitable error: %v", expect.suitableErr)
}
assert.Equal(tCtx, expect.isSuitable, isSuitable, "is suitable")
driverName, allocation, err := controller.allocate(tCtx, nodeName, tc.resources)
if err != nil {
if expect.allocateErr == "" {
tCtx.Fatalf("unexpected allocate error: %v", err)
}
require.Equal(tCtx, expect.allocateErr, err.Error())
return
}
if expect.allocateErr != "" {
tCtx.Fatalf("did not get expected allocate error: %v", expect.allocateErr)
}
assert.Equal(tCtx, expect.driverName, driverName, "driver name")
assert.Equal(tCtx, expect.allocation, allocation)
})
}
})
}
}