k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources/namedresourcesmodel_test.go (about)

     1  /*
     2  Copyright 2024 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package namedresources
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  
    25  	resourceapi "k8s.io/api/resource/v1alpha2"
    26  	"k8s.io/kubernetes/test/utils/ktesting"
    27  	"k8s.io/utils/ptr"
    28  )
    29  
    30  func instance(allocated bool, name string, attributes ...resourceapi.NamedResourcesAttribute) InstanceAllocation {
    31  	return InstanceAllocation{
    32  		Allocated: allocated,
    33  		Instance: &resourceapi.NamedResourcesInstance{
    34  			Name:       name,
    35  			Attributes: attributes,
    36  		},
    37  	}
    38  }
    39  
    40  func TestModel(t *testing.T) {
    41  	testcases := map[string]struct {
    42  		resources   []*resourceapi.NamedResourcesResources
    43  		allocations []*resourceapi.NamedResourcesAllocationResult
    44  
    45  		expectModel Model
    46  	}{
    47  		"empty": {},
    48  
    49  		"nil": {
    50  			resources:   []*resourceapi.NamedResourcesResources{nil},
    51  			allocations: []*resourceapi.NamedResourcesAllocationResult{nil},
    52  		},
    53  
    54  		"available": {
    55  			resources: []*resourceapi.NamedResourcesResources{
    56  				{
    57  					Instances: []resourceapi.NamedResourcesInstance{
    58  						{Name: "a"},
    59  						{Name: "b"},
    60  					},
    61  				},
    62  				{
    63  					Instances: []resourceapi.NamedResourcesInstance{
    64  						{Name: "x"},
    65  						{Name: "y"},
    66  					},
    67  				},
    68  			},
    69  
    70  			expectModel: Model{Instances: []InstanceAllocation{instance(false, "a"), instance(false, "b"), instance(false, "x"), instance(false, "y")}},
    71  		},
    72  
    73  		"allocated": {
    74  			resources: []*resourceapi.NamedResourcesResources{
    75  				{
    76  					Instances: []resourceapi.NamedResourcesInstance{
    77  						{Name: "a"},
    78  						{Name: "b"},
    79  					},
    80  				},
    81  				{
    82  					Instances: []resourceapi.NamedResourcesInstance{
    83  						{Name: "x"},
    84  						{Name: "y"},
    85  					},
    86  				},
    87  			},
    88  			allocations: []*resourceapi.NamedResourcesAllocationResult{
    89  				{
    90  					Name: "something-else",
    91  				},
    92  				{
    93  					Name: "a",
    94  				},
    95  			},
    96  
    97  			expectModel: Model{Instances: []InstanceAllocation{instance(true, "a"), instance(false, "b"), instance(false, "x"), instance(false, "y")}},
    98  		},
    99  	}
   100  
   101  	for name, tc := range testcases {
   102  		t.Run(name, func(t *testing.T) {
   103  			var actualModel Model
   104  			for _, resources := range tc.resources {
   105  				AddResources(&actualModel, resources)
   106  			}
   107  			for _, allocation := range tc.allocations {
   108  				AddAllocation(&actualModel, allocation)
   109  			}
   110  
   111  			require.Equal(t, tc.expectModel, actualModel)
   112  		})
   113  	}
   114  
   115  }
   116  
   117  func TestController(t *testing.T) {
   118  	filterAny := &resourceapi.NamedResourcesFilter{
   119  		Selector: "true",
   120  	}
   121  	filterNone := &resourceapi.NamedResourcesFilter{
   122  		Selector: "false",
   123  	}
   124  	filterBrokenType := &resourceapi.NamedResourcesFilter{
   125  		Selector: "1",
   126  	}
   127  	filterBrokenEvaluation := &resourceapi.NamedResourcesFilter{
   128  		Selector: `attributes.bool["no-such-attribute"]`,
   129  	}
   130  	filterAttribute := &resourceapi.NamedResourcesFilter{
   131  		Selector: `attributes.bool["usable"]`,
   132  	}
   133  
   134  	requestAny := &resourceapi.NamedResourcesRequest{
   135  		Selector: "true",
   136  	}
   137  	requestNone := &resourceapi.NamedResourcesRequest{
   138  		Selector: "false",
   139  	}
   140  	requestBrokenType := &resourceapi.NamedResourcesRequest{
   141  		Selector: "1",
   142  	}
   143  	requestBrokenEvaluation := &resourceapi.NamedResourcesRequest{
   144  		Selector: `attributes.bool["no-such-attribute"]`,
   145  	}
   146  	requestAttribute := &resourceapi.NamedResourcesRequest{
   147  		Selector: `attributes.bool["usable"]`,
   148  	}
   149  
   150  	instance1 := "instance-1"
   151  	oneInstance := Model{
   152  		Instances: []InstanceAllocation{{
   153  			Instance: &resourceapi.NamedResourcesInstance{
   154  				Name: instance1,
   155  			},
   156  		}},
   157  	}
   158  
   159  	instance2 := "instance-2"
   160  	twoInstances := Model{
   161  		Instances: []InstanceAllocation{
   162  			{
   163  				Instance: &resourceapi.NamedResourcesInstance{
   164  					Name: instance1,
   165  					Attributes: []resourceapi.NamedResourcesAttribute{{
   166  						Name: "usable",
   167  						NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{
   168  							BoolValue: ptr.To(false),
   169  						},
   170  					}},
   171  				},
   172  			},
   173  			{
   174  				Instance: &resourceapi.NamedResourcesInstance{
   175  					Name: instance2,
   176  					Attributes: []resourceapi.NamedResourcesAttribute{{
   177  						Name: "usable",
   178  						NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{
   179  							BoolValue: ptr.To(true),
   180  						},
   181  					}},
   182  				},
   183  			},
   184  		},
   185  	}
   186  
   187  	testcases := map[string]struct {
   188  		model    Model
   189  		filter   *resourceapi.NamedResourcesFilter
   190  		requests []*resourceapi.NamedResourcesRequest
   191  
   192  		expectCreateErr   bool
   193  		expectAllocation  []string
   194  		expectAllocateErr bool
   195  	}{
   196  		"empty": {},
   197  
   198  		"broken-filter": {
   199  			filter: filterBrokenType,
   200  
   201  			expectCreateErr: true,
   202  		},
   203  
   204  		"broken-request": {
   205  			requests: []*resourceapi.NamedResourcesRequest{requestBrokenType},
   206  
   207  			expectCreateErr: true,
   208  		},
   209  
   210  		"no-resources": {
   211  			filter:   filterAny,
   212  			requests: []*resourceapi.NamedResourcesRequest{requestAny},
   213  
   214  			expectAllocateErr: true,
   215  		},
   216  
   217  		"okay": {
   218  			model:    oneInstance,
   219  			filter:   filterAny,
   220  			requests: []*resourceapi.NamedResourcesRequest{requestAny},
   221  
   222  			expectAllocation: []string{instance1},
   223  		},
   224  
   225  		"filter-mismatch": {
   226  			model:    oneInstance,
   227  			filter:   filterNone,
   228  			requests: []*resourceapi.NamedResourcesRequest{requestAny},
   229  
   230  			expectAllocateErr: true,
   231  		},
   232  
   233  		"request-mismatch": {
   234  			model:    oneInstance,
   235  			filter:   filterAny,
   236  			requests: []*resourceapi.NamedResourcesRequest{requestNone},
   237  
   238  			expectAllocateErr: true,
   239  		},
   240  
   241  		"many": {
   242  			model:    twoInstances,
   243  			filter:   filterAny,
   244  			requests: []*resourceapi.NamedResourcesRequest{requestAny, requestAny},
   245  
   246  			expectAllocation: []string{instance1, instance2},
   247  		},
   248  
   249  		"too-many": {
   250  			model:    oneInstance,
   251  			filter:   filterAny,
   252  			requests: []*resourceapi.NamedResourcesRequest{requestAny, requestAny},
   253  
   254  			expectAllocateErr: true,
   255  		},
   256  
   257  		"filter-evaluation-error": {
   258  			model:    oneInstance,
   259  			filter:   filterBrokenEvaluation,
   260  			requests: []*resourceapi.NamedResourcesRequest{requestAny},
   261  
   262  			expectAllocateErr: true,
   263  		},
   264  
   265  		"request-evaluation-error": {
   266  			model:    oneInstance,
   267  			filter:   filterAny,
   268  			requests: []*resourceapi.NamedResourcesRequest{requestBrokenEvaluation},
   269  
   270  			expectAllocateErr: true,
   271  		},
   272  
   273  		"filter-attribute": {
   274  			model:    twoInstances,
   275  			filter:   filterAttribute,
   276  			requests: []*resourceapi.NamedResourcesRequest{requestAny},
   277  
   278  			expectAllocation: []string{instance2},
   279  		},
   280  
   281  		"request-attribute": {
   282  			model:    twoInstances,
   283  			filter:   filterAny,
   284  			requests: []*resourceapi.NamedResourcesRequest{requestAttribute},
   285  
   286  			expectAllocation: []string{instance2},
   287  		},
   288  	}
   289  
   290  	for name, tc := range testcases {
   291  		t.Run(name, func(t *testing.T) {
   292  			tCtx := ktesting.Init(t)
   293  
   294  			controller, createErr := NewClaimController(tc.filter, tc.requests)
   295  			if createErr != nil {
   296  				if !tc.expectCreateErr {
   297  					tCtx.Fatalf("unexpected create error: %v", createErr)
   298  				}
   299  				return
   300  			}
   301  			if tc.expectCreateErr {
   302  				tCtx.Fatalf("did not get expected create error")
   303  			}
   304  
   305  			allocation, createErr := controller.Allocate(tCtx, tc.model)
   306  			if createErr != nil {
   307  				if !tc.expectAllocateErr {
   308  					tCtx.Fatalf("unexpected allocate error: %v", createErr)
   309  				}
   310  				return
   311  			}
   312  			if tc.expectAllocateErr {
   313  				tCtx.Fatalf("did not get expected allocate error")
   314  			}
   315  
   316  			expectAllocation := []*resourceapi.NamedResourcesAllocationResult{}
   317  			for _, name := range tc.expectAllocation {
   318  				expectAllocation = append(expectAllocation, &resourceapi.NamedResourcesAllocationResult{Name: name})
   319  			}
   320  			require.Equal(tCtx, expectAllocation, allocation)
   321  
   322  			isSuitable, isSuitableErr := controller.NodeIsSuitable(tCtx, tc.model)
   323  			assert.Equal(tCtx, len(expectAllocation) == len(tc.requests), isSuitable, "is suitable")
   324  			assert.Equal(tCtx, createErr, isSuitableErr)
   325  		})
   326  	}
   327  }