k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/dynamicresources/structuredparameters_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 dynamicresources
    18  
    19  import (
    20  	"errors"
    21  	"sync"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	v1 "k8s.io/api/core/v1"
    28  	resourceapi "k8s.io/api/resource/v1alpha2"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/labels"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	namedresourcesmodel "k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources"
    34  	"k8s.io/kubernetes/test/utils/ktesting"
    35  	"k8s.io/utils/ptr"
    36  )
    37  
    38  func TestModel(t *testing.T) {
    39  	testcases := map[string]struct {
    40  		slices   resourceSliceLister
    41  		claims   assumeCacheLister
    42  		inFlight map[types.UID]resourceapi.ResourceClaimStatus
    43  
    44  		wantResources resources
    45  		wantErr       bool
    46  	}{
    47  		"empty": {},
    48  
    49  		"slice-list-error": {
    50  			slices: sliceError("slice list error"),
    51  
    52  			wantErr: true,
    53  		},
    54  
    55  		"unknown-model": {
    56  			slices: sliceList{
    57  				&resourceapi.ResourceSlice{
    58  					NodeName:      "node",
    59  					DriverName:    "driver",
    60  					ResourceModel: resourceapi.ResourceModel{ /* empty! */ },
    61  				},
    62  			},
    63  
    64  			// Not an error. It is safe to ignore unknown resources until a claim requests them.
    65  			// The unknown model in that claim then triggers an error for that claim.
    66  			wantResources: resources{"node": map[string]ResourceModels{
    67  				"driver": {
    68  					NamedResources: namedresourcesmodel.Model{},
    69  				},
    70  			}},
    71  		},
    72  
    73  		"one-instance": {
    74  			slices: sliceList{
    75  				&resourceapi.ResourceSlice{
    76  					NodeName:   "node",
    77  					DriverName: "driver",
    78  					ResourceModel: resourceapi.ResourceModel{
    79  						NamedResources: &resourceapi.NamedResourcesResources{
    80  							Instances: []resourceapi.NamedResourcesInstance{{
    81  								Name: "one",
    82  								Attributes: []resourceapi.NamedResourcesAttribute{{
    83  									Name: "size",
    84  									NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{
    85  										IntValue: ptr.To(int64(1)),
    86  									},
    87  								}},
    88  							}},
    89  						},
    90  					},
    91  				},
    92  			},
    93  
    94  			wantResources: resources{"node": map[string]ResourceModels{
    95  				"driver": {
    96  					NamedResources: namedresourcesmodel.Model{
    97  						Instances: []namedresourcesmodel.InstanceAllocation{
    98  							{
    99  								Instance: &resourceapi.NamedResourcesInstance{
   100  									Name: "one",
   101  									Attributes: []resourceapi.NamedResourcesAttribute{{
   102  										Name: "size",
   103  										NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{
   104  											IntValue: ptr.To(int64(1)),
   105  										},
   106  									}},
   107  								},
   108  							},
   109  						},
   110  					},
   111  				},
   112  			}},
   113  		},
   114  
   115  		"two-instances": {
   116  			slices: sliceList{
   117  				&resourceapi.ResourceSlice{
   118  					NodeName:   "node",
   119  					DriverName: "driver",
   120  					ResourceModel: resourceapi.ResourceModel{
   121  						NamedResources: &resourceapi.NamedResourcesResources{
   122  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   123  						},
   124  					},
   125  				},
   126  			},
   127  
   128  			wantResources: resources{"node": map[string]ResourceModels{
   129  				"driver": {
   130  					NamedResources: namedresourcesmodel.Model{
   131  						Instances: []namedresourcesmodel.InstanceAllocation{
   132  							{
   133  								Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   134  							},
   135  							{
   136  								Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   137  							},
   138  						},
   139  					},
   140  				},
   141  			}},
   142  		},
   143  
   144  		"two-nodes": {
   145  			slices: sliceList{
   146  				&resourceapi.ResourceSlice{
   147  					NodeName:   "node-a",
   148  					DriverName: "driver",
   149  					ResourceModel: resourceapi.ResourceModel{
   150  						NamedResources: &resourceapi.NamedResourcesResources{
   151  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   152  						},
   153  					},
   154  				},
   155  				&resourceapi.ResourceSlice{
   156  					NodeName:   "node-b",
   157  					DriverName: "driver",
   158  					ResourceModel: resourceapi.ResourceModel{
   159  						NamedResources: &resourceapi.NamedResourcesResources{
   160  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   161  						},
   162  					},
   163  				},
   164  			},
   165  
   166  			wantResources: resources{
   167  				"node-a": map[string]ResourceModels{
   168  					"driver": {
   169  						NamedResources: namedresourcesmodel.Model{
   170  							Instances: []namedresourcesmodel.InstanceAllocation{
   171  								{
   172  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   173  								},
   174  								{
   175  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   176  								},
   177  							},
   178  						},
   179  					},
   180  				},
   181  				"node-b": map[string]ResourceModels{
   182  					"driver": {
   183  						NamedResources: namedresourcesmodel.Model{
   184  							Instances: []namedresourcesmodel.InstanceAllocation{
   185  								{
   186  									Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
   187  								},
   188  								{
   189  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   190  								},
   191  							},
   192  						},
   193  					},
   194  				},
   195  			},
   196  		},
   197  
   198  		"two-nodes-two-drivers": {
   199  			slices: sliceList{
   200  				&resourceapi.ResourceSlice{
   201  					NodeName:   "node-a",
   202  					DriverName: "driver-a",
   203  					ResourceModel: resourceapi.ResourceModel{
   204  						NamedResources: &resourceapi.NamedResourcesResources{
   205  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   206  						},
   207  					},
   208  				},
   209  				&resourceapi.ResourceSlice{
   210  					NodeName:   "node-a",
   211  					DriverName: "driver-b",
   212  					ResourceModel: resourceapi.ResourceModel{
   213  						NamedResources: &resourceapi.NamedResourcesResources{
   214  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   215  						},
   216  					},
   217  				},
   218  				&resourceapi.ResourceSlice{
   219  					NodeName:   "node-b",
   220  					DriverName: "driver-a",
   221  					ResourceModel: resourceapi.ResourceModel{
   222  						NamedResources: &resourceapi.NamedResourcesResources{
   223  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   224  						},
   225  					},
   226  				},
   227  				&resourceapi.ResourceSlice{
   228  					NodeName:   "node-b",
   229  					DriverName: "driver-b",
   230  					ResourceModel: resourceapi.ResourceModel{
   231  						NamedResources: &resourceapi.NamedResourcesResources{
   232  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   233  						},
   234  					},
   235  				},
   236  			},
   237  
   238  			wantResources: resources{
   239  				"node-a": map[string]ResourceModels{
   240  					"driver-a": {
   241  						NamedResources: namedresourcesmodel.Model{
   242  							Instances: []namedresourcesmodel.InstanceAllocation{
   243  								{
   244  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   245  								},
   246  								{
   247  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   248  								},
   249  							},
   250  						},
   251  					},
   252  					"driver-b": {
   253  						NamedResources: namedresourcesmodel.Model{
   254  							Instances: []namedresourcesmodel.InstanceAllocation{
   255  								{
   256  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   257  								},
   258  								{
   259  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   260  								},
   261  							},
   262  						},
   263  					},
   264  				},
   265  				"node-b": map[string]ResourceModels{
   266  					"driver-a": {
   267  						NamedResources: namedresourcesmodel.Model{
   268  							Instances: []namedresourcesmodel.InstanceAllocation{
   269  								{
   270  									Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
   271  								},
   272  								{
   273  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   274  								},
   275  							},
   276  						},
   277  					},
   278  					"driver-b": {
   279  						NamedResources: namedresourcesmodel.Model{
   280  							Instances: []namedresourcesmodel.InstanceAllocation{
   281  								{
   282  									Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
   283  								},
   284  								{
   285  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   286  								},
   287  							},
   288  						},
   289  					},
   290  				},
   291  			},
   292  		},
   293  
   294  		"in-use-simple": {
   295  			slices: sliceList{
   296  				&resourceapi.ResourceSlice{
   297  					NodeName:   "node-a",
   298  					DriverName: "driver-a",
   299  					ResourceModel: resourceapi.ResourceModel{
   300  						NamedResources: &resourceapi.NamedResourcesResources{
   301  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   302  						},
   303  					},
   304  				},
   305  				&resourceapi.ResourceSlice{
   306  					NodeName:   "node-a",
   307  					DriverName: "driver-b",
   308  					ResourceModel: resourceapi.ResourceModel{
   309  						NamedResources: &resourceapi.NamedResourcesResources{
   310  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   311  						},
   312  					},
   313  				},
   314  				&resourceapi.ResourceSlice{
   315  					NodeName:   "node-b",
   316  					DriverName: "driver-a",
   317  					ResourceModel: resourceapi.ResourceModel{
   318  						NamedResources: &resourceapi.NamedResourcesResources{
   319  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   320  						},
   321  					},
   322  				},
   323  				&resourceapi.ResourceSlice{
   324  					NodeName:   "node-b",
   325  					DriverName: "driver-b",
   326  					ResourceModel: resourceapi.ResourceModel{
   327  						NamedResources: &resourceapi.NamedResourcesResources{
   328  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   329  						},
   330  					},
   331  				},
   332  			},
   333  
   334  			claims: claimList{
   335  				&resourceapi.ResourceClaim{
   336  					/* not allocated */
   337  				},
   338  				&resourceapi.ResourceClaim{
   339  					Status: resourceapi.ResourceClaimStatus{
   340  						DriverName: "driver-a",
   341  						Allocation: &resourceapi.AllocationResult{
   342  							ResourceHandles: []resourceapi.ResourceHandle{{
   343  								// Claims not allocated via structured parameters can be ignored.
   344  							}},
   345  						},
   346  					},
   347  				},
   348  				&resourceapi.ResourceClaim{
   349  					Status: resourceapi.ResourceClaimStatus{
   350  						DriverName: "driver-a",
   351  						Allocation: &resourceapi.AllocationResult{
   352  							ResourceHandles: []resourceapi.ResourceHandle{{
   353  								DriverName: "driver-a",
   354  								StructuredData: &resourceapi.StructuredResourceHandle{
   355  									NodeName: "node-b",
   356  									// Unknown allocations can be ignored.
   357  									Results: []resourceapi.DriverAllocationResult{{
   358  										AllocationResultModel: resourceapi.AllocationResultModel{},
   359  									}},
   360  								},
   361  							}},
   362  						},
   363  					},
   364  				},
   365  				&resourceapi.ResourceClaim{
   366  					Status: resourceapi.ResourceClaimStatus{
   367  						DriverName: "driver-a",
   368  						Allocation: &resourceapi.AllocationResult{
   369  							ResourceHandles: []resourceapi.ResourceHandle{{
   370  								DriverName: "driver-a",
   371  								StructuredData: &resourceapi.StructuredResourceHandle{
   372  									NodeName: "node-a",
   373  									Results: []resourceapi.DriverAllocationResult{{
   374  										AllocationResultModel: resourceapi.AllocationResultModel{
   375  											NamedResources: &resourceapi.NamedResourcesAllocationResult{
   376  												Name: "two",
   377  											},
   378  										},
   379  									}},
   380  								},
   381  							}},
   382  						},
   383  					},
   384  				},
   385  			},
   386  
   387  			wantResources: resources{
   388  				"node-a": map[string]ResourceModels{
   389  					"driver-a": {
   390  						NamedResources: namedresourcesmodel.Model{
   391  							Instances: []namedresourcesmodel.InstanceAllocation{
   392  								{
   393  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   394  								},
   395  								{
   396  									Allocated: true,
   397  									Instance:  &resourceapi.NamedResourcesInstance{Name: "two"},
   398  								},
   399  							},
   400  						},
   401  					},
   402  					"driver-b": {
   403  						NamedResources: namedresourcesmodel.Model{
   404  							Instances: []namedresourcesmodel.InstanceAllocation{
   405  								{
   406  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   407  								},
   408  								{
   409  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   410  								},
   411  							},
   412  						},
   413  					},
   414  				},
   415  				"node-b": map[string]ResourceModels{
   416  					"driver-a": {
   417  						NamedResources: namedresourcesmodel.Model{
   418  							Instances: []namedresourcesmodel.InstanceAllocation{
   419  								{
   420  									Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
   421  								},
   422  								{
   423  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   424  								},
   425  							},
   426  						},
   427  					},
   428  					"driver-b": {
   429  						NamedResources: namedresourcesmodel.Model{
   430  							Instances: []namedresourcesmodel.InstanceAllocation{
   431  								{
   432  									Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
   433  								},
   434  								{
   435  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   436  								},
   437  							},
   438  						},
   439  					},
   440  				},
   441  			},
   442  		},
   443  
   444  		"in-use-meta-driver": {
   445  			slices: sliceList{
   446  				&resourceapi.ResourceSlice{
   447  					NodeName:   "node-a",
   448  					DriverName: "driver-a",
   449  					ResourceModel: resourceapi.ResourceModel{
   450  						NamedResources: &resourceapi.NamedResourcesResources{
   451  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   452  						},
   453  					},
   454  				},
   455  				&resourceapi.ResourceSlice{
   456  					NodeName:   "node-a",
   457  					DriverName: "driver-b",
   458  					ResourceModel: resourceapi.ResourceModel{
   459  						NamedResources: &resourceapi.NamedResourcesResources{
   460  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   461  						},
   462  					},
   463  				},
   464  				&resourceapi.ResourceSlice{
   465  					NodeName:   "node-b",
   466  					DriverName: "driver-a",
   467  					ResourceModel: resourceapi.ResourceModel{
   468  						NamedResources: &resourceapi.NamedResourcesResources{
   469  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   470  						},
   471  					},
   472  				},
   473  				&resourceapi.ResourceSlice{
   474  					NodeName:   "node-b",
   475  					DriverName: "driver-b",
   476  					ResourceModel: resourceapi.ResourceModel{
   477  						NamedResources: &resourceapi.NamedResourcesResources{
   478  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   479  						},
   480  					},
   481  				},
   482  			},
   483  
   484  			claims: claimList{
   485  				&resourceapi.ResourceClaim{
   486  					Status: resourceapi.ResourceClaimStatus{
   487  						DriverName: "meta-driver",
   488  						Allocation: &resourceapi.AllocationResult{
   489  							ResourceHandles: []resourceapi.ResourceHandle{{
   490  								DriverName: "driver-b",
   491  								StructuredData: &resourceapi.StructuredResourceHandle{
   492  									NodeName: "node-b",
   493  									Results: []resourceapi.DriverAllocationResult{{
   494  										AllocationResultModel: resourceapi.AllocationResultModel{
   495  											NamedResources: &resourceapi.NamedResourcesAllocationResult{
   496  												Name: "X",
   497  											},
   498  										},
   499  									}},
   500  								},
   501  							}},
   502  						},
   503  					},
   504  				},
   505  			},
   506  
   507  			wantResources: resources{
   508  				"node-a": map[string]ResourceModels{
   509  					"driver-a": {
   510  						NamedResources: namedresourcesmodel.Model{
   511  							Instances: []namedresourcesmodel.InstanceAllocation{
   512  								{
   513  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   514  								},
   515  								{
   516  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   517  								},
   518  							},
   519  						},
   520  					},
   521  					"driver-b": {
   522  						NamedResources: namedresourcesmodel.Model{
   523  							Instances: []namedresourcesmodel.InstanceAllocation{
   524  								{
   525  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   526  								},
   527  								{
   528  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   529  								},
   530  							},
   531  						},
   532  					},
   533  				},
   534  				"node-b": map[string]ResourceModels{
   535  					"driver-a": {
   536  						NamedResources: namedresourcesmodel.Model{
   537  							Instances: []namedresourcesmodel.InstanceAllocation{
   538  								{
   539  									Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
   540  								},
   541  								{
   542  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   543  								},
   544  							},
   545  						},
   546  					},
   547  					"driver-b": {
   548  						NamedResources: namedresourcesmodel.Model{
   549  							Instances: []namedresourcesmodel.InstanceAllocation{
   550  								{
   551  									Allocated: true,
   552  									Instance:  &resourceapi.NamedResourcesInstance{Name: "X"},
   553  								},
   554  								{
   555  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   556  								},
   557  							},
   558  						},
   559  					},
   560  				},
   561  			},
   562  		},
   563  
   564  		"in-use-many-results": {
   565  			slices: sliceList{
   566  				&resourceapi.ResourceSlice{
   567  					NodeName:   "node-a",
   568  					DriverName: "driver-a",
   569  					ResourceModel: resourceapi.ResourceModel{
   570  						NamedResources: &resourceapi.NamedResourcesResources{
   571  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   572  						},
   573  					},
   574  				},
   575  				&resourceapi.ResourceSlice{
   576  					NodeName:   "node-a",
   577  					DriverName: "driver-b",
   578  					ResourceModel: resourceapi.ResourceModel{
   579  						NamedResources: &resourceapi.NamedResourcesResources{
   580  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   581  						},
   582  					},
   583  				},
   584  				&resourceapi.ResourceSlice{
   585  					NodeName:   "node-b",
   586  					DriverName: "driver-a",
   587  					ResourceModel: resourceapi.ResourceModel{
   588  						NamedResources: &resourceapi.NamedResourcesResources{
   589  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   590  						},
   591  					},
   592  				},
   593  				&resourceapi.ResourceSlice{
   594  					NodeName:   "node-b",
   595  					DriverName: "driver-b",
   596  					ResourceModel: resourceapi.ResourceModel{
   597  						NamedResources: &resourceapi.NamedResourcesResources{
   598  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   599  						},
   600  					},
   601  				},
   602  			},
   603  
   604  			claims: claimList{
   605  				&resourceapi.ResourceClaim{
   606  					Status: resourceapi.ResourceClaimStatus{
   607  						DriverName: "driver-a",
   608  						Allocation: &resourceapi.AllocationResult{
   609  							ResourceHandles: []resourceapi.ResourceHandle{{
   610  								DriverName: "driver-a",
   611  								StructuredData: &resourceapi.StructuredResourceHandle{
   612  									NodeName: "node-a",
   613  									Results: []resourceapi.DriverAllocationResult{
   614  										{
   615  											AllocationResultModel: resourceapi.AllocationResultModel{
   616  												NamedResources: &resourceapi.NamedResourcesAllocationResult{
   617  													Name: "one",
   618  												},
   619  											},
   620  										},
   621  										{
   622  											AllocationResultModel: resourceapi.AllocationResultModel{
   623  												NamedResources: &resourceapi.NamedResourcesAllocationResult{
   624  													Name: "two",
   625  												},
   626  											},
   627  										},
   628  									},
   629  								},
   630  							}},
   631  						},
   632  					},
   633  				},
   634  			},
   635  
   636  			wantResources: resources{
   637  				"node-a": map[string]ResourceModels{
   638  					"driver-a": {
   639  						NamedResources: namedresourcesmodel.Model{
   640  							Instances: []namedresourcesmodel.InstanceAllocation{
   641  								{
   642  									Allocated: true,
   643  									Instance:  &resourceapi.NamedResourcesInstance{Name: "one"},
   644  								},
   645  								{
   646  									Allocated: true,
   647  									Instance:  &resourceapi.NamedResourcesInstance{Name: "two"},
   648  								},
   649  							},
   650  						},
   651  					},
   652  					"driver-b": {
   653  						NamedResources: namedresourcesmodel.Model{
   654  							Instances: []namedresourcesmodel.InstanceAllocation{
   655  								{
   656  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   657  								},
   658  								{
   659  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   660  								},
   661  							},
   662  						},
   663  					},
   664  				},
   665  				"node-b": map[string]ResourceModels{
   666  					"driver-a": {
   667  						NamedResources: namedresourcesmodel.Model{
   668  							Instances: []namedresourcesmodel.InstanceAllocation{
   669  								{
   670  									Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
   671  								},
   672  								{
   673  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   674  								},
   675  							},
   676  						},
   677  					},
   678  					"driver-b": {
   679  						NamedResources: namedresourcesmodel.Model{
   680  							Instances: []namedresourcesmodel.InstanceAllocation{
   681  								{
   682  									Instance: &resourceapi.NamedResourcesInstance{Name: "X"},
   683  								},
   684  								{
   685  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   686  								},
   687  							},
   688  						},
   689  					},
   690  				},
   691  			},
   692  		},
   693  
   694  		"in-use-many-handles": {
   695  			slices: sliceList{
   696  				&resourceapi.ResourceSlice{
   697  					NodeName:   "node-a",
   698  					DriverName: "driver-a",
   699  					ResourceModel: resourceapi.ResourceModel{
   700  						NamedResources: &resourceapi.NamedResourcesResources{
   701  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   702  						},
   703  					},
   704  				},
   705  				&resourceapi.ResourceSlice{
   706  					NodeName:   "node-a",
   707  					DriverName: "driver-b",
   708  					ResourceModel: resourceapi.ResourceModel{
   709  						NamedResources: &resourceapi.NamedResourcesResources{
   710  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}},
   711  						},
   712  					},
   713  				},
   714  				&resourceapi.ResourceSlice{
   715  					NodeName:   "node-b",
   716  					DriverName: "driver-a",
   717  					ResourceModel: resourceapi.ResourceModel{
   718  						NamedResources: &resourceapi.NamedResourcesResources{
   719  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   720  						},
   721  					},
   722  				},
   723  				&resourceapi.ResourceSlice{
   724  					NodeName:   "node-b",
   725  					DriverName: "driver-b",
   726  					ResourceModel: resourceapi.ResourceModel{
   727  						NamedResources: &resourceapi.NamedResourcesResources{
   728  							Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}},
   729  						},
   730  					},
   731  				},
   732  			},
   733  
   734  			claims: claimList{
   735  				&resourceapi.ResourceClaim{
   736  					Status: resourceapi.ResourceClaimStatus{
   737  						DriverName: "meta-driver",
   738  						Allocation: &resourceapi.AllocationResult{
   739  							ResourceHandles: []resourceapi.ResourceHandle{
   740  								{
   741  									DriverName: "driver-a",
   742  									StructuredData: &resourceapi.StructuredResourceHandle{
   743  										NodeName: "node-b",
   744  										Results: []resourceapi.DriverAllocationResult{{
   745  											AllocationResultModel: resourceapi.AllocationResultModel{
   746  												NamedResources: &resourceapi.NamedResourcesAllocationResult{
   747  													Name: "X",
   748  												},
   749  											},
   750  										}},
   751  									},
   752  								},
   753  								{
   754  									DriverName: "driver-b",
   755  									StructuredData: &resourceapi.StructuredResourceHandle{
   756  										NodeName: "node-b",
   757  										Results: []resourceapi.DriverAllocationResult{{
   758  											AllocationResultModel: resourceapi.AllocationResultModel{
   759  												NamedResources: &resourceapi.NamedResourcesAllocationResult{
   760  													Name: "X",
   761  												},
   762  											},
   763  										}},
   764  									},
   765  								},
   766  							},
   767  						},
   768  					},
   769  				},
   770  			},
   771  
   772  			wantResources: resources{
   773  				"node-a": map[string]ResourceModels{
   774  					"driver-a": {
   775  						NamedResources: namedresourcesmodel.Model{
   776  							Instances: []namedresourcesmodel.InstanceAllocation{
   777  								{
   778  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   779  								},
   780  								{
   781  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   782  								},
   783  							},
   784  						},
   785  					},
   786  					"driver-b": {
   787  						NamedResources: namedresourcesmodel.Model{
   788  							Instances: []namedresourcesmodel.InstanceAllocation{
   789  								{
   790  									Instance: &resourceapi.NamedResourcesInstance{Name: "one"},
   791  								},
   792  								{
   793  									Instance: &resourceapi.NamedResourcesInstance{Name: "two"},
   794  								},
   795  							},
   796  						},
   797  					},
   798  				},
   799  				"node-b": map[string]ResourceModels{
   800  					"driver-a": {
   801  						NamedResources: namedresourcesmodel.Model{
   802  							Instances: []namedresourcesmodel.InstanceAllocation{
   803  								{
   804  									Allocated: true,
   805  									Instance:  &resourceapi.NamedResourcesInstance{Name: "X"},
   806  								},
   807  								{
   808  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   809  								},
   810  							},
   811  						},
   812  					},
   813  					"driver-b": {
   814  						NamedResources: namedresourcesmodel.Model{
   815  							Instances: []namedresourcesmodel.InstanceAllocation{
   816  								{
   817  									Allocated: true,
   818  									Instance:  &resourceapi.NamedResourcesInstance{Name: "X"},
   819  								},
   820  								{
   821  									Instance: &resourceapi.NamedResourcesInstance{Name: "Y"},
   822  								},
   823  							},
   824  						},
   825  					},
   826  				},
   827  			},
   828  		},
   829  
   830  		"orphaned-allocations": {
   831  			claims: claimList{
   832  				&resourceapi.ResourceClaim{
   833  					Status: resourceapi.ResourceClaimStatus{
   834  						DriverName: "meta-driver",
   835  						Allocation: &resourceapi.AllocationResult{
   836  							ResourceHandles: []resourceapi.ResourceHandle{
   837  								{
   838  									DriverName: "driver-a",
   839  									StructuredData: &resourceapi.StructuredResourceHandle{
   840  										NodeName: "node-b",
   841  										Results: []resourceapi.DriverAllocationResult{{
   842  											AllocationResultModel: resourceapi.AllocationResultModel{
   843  												NamedResources: &resourceapi.NamedResourcesAllocationResult{
   844  													Name: "X",
   845  												},
   846  											},
   847  										}},
   848  									},
   849  								},
   850  								{
   851  									DriverName: "driver-b",
   852  									StructuredData: &resourceapi.StructuredResourceHandle{
   853  										NodeName: "node-b",
   854  										Results: []resourceapi.DriverAllocationResult{{
   855  											AllocationResultModel: resourceapi.AllocationResultModel{
   856  												NamedResources: &resourceapi.NamedResourcesAllocationResult{
   857  													Name: "X",
   858  												},
   859  											},
   860  										}},
   861  									},
   862  								},
   863  							},
   864  						},
   865  					},
   866  				},
   867  			},
   868  
   869  			wantResources: resources{
   870  				"node-b": map[string]ResourceModels{},
   871  			},
   872  		},
   873  
   874  		"in-flight": {
   875  			slices: sliceList{
   876  				&resourceapi.ResourceSlice{
   877  					NodeName:   "node",
   878  					DriverName: "driver",
   879  					ResourceModel: resourceapi.ResourceModel{
   880  						NamedResources: &resourceapi.NamedResourcesResources{
   881  							Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}},
   882  						},
   883  					},
   884  				},
   885  			},
   886  
   887  			claims: claimList{
   888  				&resourceapi.ResourceClaim{
   889  					ObjectMeta: metav1.ObjectMeta{
   890  						UID: "abc",
   891  					},
   892  					// Allocation not recorded yet.
   893  				},
   894  			},
   895  
   896  			inFlight: map[types.UID]resourceapi.ResourceClaimStatus{
   897  				"abc": {
   898  					DriverName: "driver",
   899  					Allocation: &resourceapi.AllocationResult{
   900  						ResourceHandles: []resourceapi.ResourceHandle{{
   901  							DriverName: "driver",
   902  							StructuredData: &resourceapi.StructuredResourceHandle{
   903  								NodeName: "node",
   904  								Results: []resourceapi.DriverAllocationResult{{
   905  									AllocationResultModel: resourceapi.AllocationResultModel{
   906  										NamedResources: &resourceapi.NamedResourcesAllocationResult{
   907  											Name: "one",
   908  										},
   909  									},
   910  								}},
   911  							},
   912  						}},
   913  					},
   914  				},
   915  			},
   916  
   917  			wantResources: resources{"node": map[string]ResourceModels{
   918  				"driver": {
   919  					NamedResources: namedresourcesmodel.Model{
   920  						Instances: []namedresourcesmodel.InstanceAllocation{
   921  							{
   922  								Allocated: true,
   923  								Instance:  &resourceapi.NamedResourcesInstance{Name: "one"},
   924  							},
   925  						},
   926  					},
   927  				},
   928  			}},
   929  		},
   930  	}
   931  
   932  	for name, tc := range testcases {
   933  		t.Run(name, func(t *testing.T) {
   934  			tCtx := ktesting.Init(t)
   935  
   936  			var inFlightAllocations sync.Map
   937  			for uid, claimStatus := range tc.inFlight {
   938  				inFlightAllocations.Store(uid, &resourceapi.ResourceClaim{Status: claimStatus})
   939  			}
   940  
   941  			slices := tc.slices
   942  			if slices == nil {
   943  				slices = sliceList{}
   944  			}
   945  			claims := tc.claims
   946  			if claims == nil {
   947  				claims = claimList{}
   948  			}
   949  			actualResources, actualErr := newResourceModel(tCtx.Logger(), slices, claims, &inFlightAllocations)
   950  
   951  			if actualErr != nil {
   952  				if !tc.wantErr {
   953  					tCtx.Fatalf("unexpected error: %v", actualErr)
   954  				}
   955  				return
   956  			}
   957  			if tc.wantErr {
   958  				tCtx.Fatalf("did not get expected error")
   959  			}
   960  
   961  			expectResources := tc.wantResources
   962  			if expectResources == nil {
   963  				expectResources = resources{}
   964  			}
   965  			require.Equal(tCtx, expectResources, actualResources)
   966  		})
   967  	}
   968  }
   969  
   970  type sliceList []*resourceapi.ResourceSlice
   971  
   972  func (l sliceList) List(selector labels.Selector) ([]*resourceapi.ResourceSlice, error) {
   973  	return l, nil
   974  }
   975  
   976  type sliceError string
   977  
   978  func (l sliceError) List(selector labels.Selector) ([]*resourceapi.ResourceSlice, error) {
   979  	return nil, errors.New(string(l))
   980  }
   981  
   982  type claimList []any
   983  
   984  func (l claimList) List(indexObj any) []any {
   985  	return l
   986  }
   987  
   988  func TestController(t *testing.T) {
   989  	driver1 := "driver-1"
   990  	class1 := &resourceapi.ResourceClass{
   991  		DriverName: driver1,
   992  	}
   993  
   994  	classParametersEmpty := &resourceapi.ResourceClassParameters{}
   995  	classParametersAny := &resourceapi.ResourceClassParameters{
   996  		Filters: []resourceapi.ResourceFilter{{
   997  			DriverName: driver1,
   998  			ResourceFilterModel: resourceapi.ResourceFilterModel{
   999  				NamedResources: &resourceapi.NamedResourcesFilter{
  1000  					Selector: "true",
  1001  				},
  1002  			},
  1003  		}},
  1004  	}
  1005  
  1006  	claimParametersEmpty := &resourceapi.ResourceClaimParameters{}
  1007  	claimParametersAny := &resourceapi.ResourceClaimParameters{
  1008  		DriverRequests: []resourceapi.DriverRequests{{
  1009  			DriverName: driver1,
  1010  		}},
  1011  	}
  1012  	claimParametersOne := &resourceapi.ResourceClaimParameters{
  1013  		DriverRequests: []resourceapi.DriverRequests{{
  1014  			DriverName: driver1,
  1015  			Requests: []resourceapi.ResourceRequest{{
  1016  				ResourceRequestModel: resourceapi.ResourceRequestModel{
  1017  					NamedResources: &resourceapi.NamedResourcesRequest{
  1018  						Selector: "true",
  1019  					},
  1020  				},
  1021  			}},
  1022  		}},
  1023  	}
  1024  	claimParametersBroken := &resourceapi.ResourceClaimParameters{
  1025  		DriverRequests: []resourceapi.DriverRequests{{
  1026  			DriverName: driver1,
  1027  			Requests: []resourceapi.ResourceRequest{{
  1028  				ResourceRequestModel: resourceapi.ResourceRequestModel{
  1029  					NamedResources: &resourceapi.NamedResourcesRequest{
  1030  						Selector: `attributes.bool["no-such-attribute"]`,
  1031  					},
  1032  				},
  1033  			}},
  1034  		}},
  1035  	}
  1036  
  1037  	instance1 := "instance-1"
  1038  
  1039  	node1 := "node-1"
  1040  	node1Selector := &v1.NodeSelector{
  1041  		NodeSelectorTerms: []v1.NodeSelectorTerm{{
  1042  			MatchExpressions: []v1.NodeSelectorRequirement{{
  1043  				Key:      "kubernetes.io/hostname",
  1044  				Operator: v1.NodeSelectorOpIn,
  1045  				Values:   []string{node1},
  1046  			}},
  1047  		}},
  1048  	}
  1049  	node1Resources := resources{node1: map[string]ResourceModels{
  1050  		driver1: {
  1051  			NamedResources: namedresourcesmodel.Model{
  1052  				Instances: []namedresourcesmodel.InstanceAllocation{{
  1053  					Instance: &resourceapi.NamedResourcesInstance{
  1054  						Name: instance1,
  1055  					},
  1056  				}},
  1057  			},
  1058  		},
  1059  	}}
  1060  	node1Allocation := &resourceapi.AllocationResult{
  1061  		AvailableOnNodes: node1Selector,
  1062  	}
  1063  
  1064  	instance1Allocation := &resourceapi.AllocationResult{
  1065  		AvailableOnNodes: node1Selector,
  1066  		ResourceHandles: []resourceapi.ResourceHandle{{
  1067  			DriverName: driver1,
  1068  			StructuredData: &resourceapi.StructuredResourceHandle{
  1069  				NodeName: node1,
  1070  				Results: []resourceapi.DriverAllocationResult{{
  1071  					AllocationResultModel: resourceapi.AllocationResultModel{
  1072  						NamedResources: &resourceapi.NamedResourcesAllocationResult{
  1073  							Name: instance1,
  1074  						},
  1075  					},
  1076  				}},
  1077  			},
  1078  		}},
  1079  	}
  1080  
  1081  	type nodeResult struct {
  1082  		isSuitable  bool
  1083  		suitableErr string
  1084  
  1085  		driverName  string
  1086  		allocation  *resourceapi.AllocationResult
  1087  		allocateErr string
  1088  	}
  1089  	type nodeResults map[string]nodeResult
  1090  
  1091  	testcases := map[string]struct {
  1092  		resources       resources
  1093  		class           *resourceapi.ResourceClass
  1094  		classParameters *resourceapi.ResourceClassParameters
  1095  		claimParameters *resourceapi.ResourceClaimParameters
  1096  
  1097  		expectCreateErr   bool
  1098  		expectNodeResults nodeResults
  1099  	}{
  1100  		"empty": {
  1101  			class:           class1,
  1102  			classParameters: classParametersEmpty,
  1103  			claimParameters: claimParametersEmpty,
  1104  
  1105  			expectNodeResults: nodeResults{
  1106  				node1: {isSuitable: true, driverName: driver1, allocation: node1Allocation},
  1107  			},
  1108  		},
  1109  
  1110  		"any": {
  1111  			class:           class1,
  1112  			classParameters: classParametersEmpty,
  1113  			claimParameters: claimParametersAny,
  1114  
  1115  			expectNodeResults: nodeResults{
  1116  				node1: {isSuitable: true, driverName: driver1, allocation: node1Allocation},
  1117  			},
  1118  		},
  1119  
  1120  		"missing-model": {
  1121  			class:           class1,
  1122  			classParameters: classParametersEmpty,
  1123  			claimParameters: &resourceapi.ResourceClaimParameters{
  1124  				DriverRequests: []resourceapi.DriverRequests{{
  1125  					Requests: []resourceapi.ResourceRequest{{ /* empty model */ }},
  1126  				}},
  1127  			},
  1128  
  1129  			expectCreateErr: true,
  1130  		},
  1131  
  1132  		"no-resources": {
  1133  			class:           class1,
  1134  			classParameters: classParametersEmpty,
  1135  			claimParameters: claimParametersOne,
  1136  
  1137  			expectNodeResults: nodeResults{
  1138  				node1: {isSuitable: false, allocateErr: "allocating via named resources structured model: insufficient resources"},
  1139  			},
  1140  		},
  1141  
  1142  		"have-resources": {
  1143  			resources:       node1Resources,
  1144  			class:           class1,
  1145  			classParameters: classParametersEmpty,
  1146  			claimParameters: claimParametersOne,
  1147  
  1148  			expectNodeResults: nodeResults{
  1149  				node1: {isSuitable: true, driverName: driver1, allocation: instance1Allocation},
  1150  			},
  1151  		},
  1152  
  1153  		"broken-cel": {
  1154  			resources:       node1Resources,
  1155  			class:           class1,
  1156  			classParameters: classParametersEmpty,
  1157  			claimParameters: claimParametersBroken,
  1158  
  1159  			expectNodeResults: nodeResults{
  1160  				node1: {suitableErr: `checking node "node-1" and resources of driver "driver-1": evaluate request CEL expression: no such key: no-such-attribute`},
  1161  			},
  1162  		},
  1163  
  1164  		"class-filter": {
  1165  			resources:       node1Resources,
  1166  			class:           class1,
  1167  			classParameters: classParametersAny,
  1168  			claimParameters: claimParametersOne,
  1169  
  1170  			expectNodeResults: nodeResults{
  1171  				node1: {isSuitable: true, driverName: driver1, allocation: instance1Allocation},
  1172  			},
  1173  		},
  1174  
  1175  		"vendor-parameters": {
  1176  			resources: node1Resources,
  1177  			class:     class1,
  1178  			classParameters: func() *resourceapi.ResourceClassParameters {
  1179  				parameters := classParametersAny.DeepCopy()
  1180  				parameters.VendorParameters = []resourceapi.VendorParameters{{
  1181  					DriverName: driver1,
  1182  					Parameters: runtime.RawExtension{Raw: []byte("class-parameters")},
  1183  				}}
  1184  				return parameters
  1185  			}(),
  1186  
  1187  			claimParameters: func() *resourceapi.ResourceClaimParameters {
  1188  				parameters := claimParametersOne.DeepCopy()
  1189  				parameters.DriverRequests[0].VendorParameters = runtime.RawExtension{Raw: []byte("claim-parameters")}
  1190  				parameters.DriverRequests[0].Requests[0].VendorParameters = runtime.RawExtension{Raw: []byte("request-parameters")}
  1191  				return parameters
  1192  			}(),
  1193  
  1194  			expectNodeResults: nodeResults{
  1195  				node1: {isSuitable: true, driverName: driver1,
  1196  					allocation: func() *resourceapi.AllocationResult {
  1197  						allocation := instance1Allocation.DeepCopy()
  1198  						allocation.ResourceHandles[0].StructuredData.VendorClassParameters = runtime.RawExtension{Raw: []byte("class-parameters")}
  1199  						allocation.ResourceHandles[0].StructuredData.VendorClaimParameters = runtime.RawExtension{Raw: []byte("claim-parameters")}
  1200  						allocation.ResourceHandles[0].StructuredData.Results[0].VendorRequestParameters = runtime.RawExtension{Raw: []byte("request-parameters")}
  1201  						return allocation
  1202  					}(),
  1203  				},
  1204  			},
  1205  		},
  1206  	}
  1207  
  1208  	for name, tc := range testcases {
  1209  		t.Run(name, func(t *testing.T) {
  1210  			tCtx := ktesting.Init(t)
  1211  
  1212  			controller, err := newClaimController(tCtx.Logger(), tc.class, tc.classParameters, tc.claimParameters)
  1213  			if err != nil {
  1214  				if !tc.expectCreateErr {
  1215  					tCtx.Fatalf("unexpected error: %v", err)
  1216  				}
  1217  				return
  1218  			}
  1219  			if tc.expectCreateErr {
  1220  				tCtx.Fatalf("did not get expected error")
  1221  			}
  1222  
  1223  			for nodeName, expect := range tc.expectNodeResults {
  1224  				t.Run(nodeName, func(t *testing.T) {
  1225  					tCtx := ktesting.Init(t)
  1226  
  1227  					isSuitable, err := controller.nodeIsSuitable(tCtx, nodeName, tc.resources)
  1228  					if err != nil {
  1229  						if expect.suitableErr == "" {
  1230  							tCtx.Fatalf("unexpected nodeIsSuitable error: %v", err)
  1231  						}
  1232  						require.Equal(tCtx, expect.suitableErr, err.Error())
  1233  						return
  1234  					}
  1235  					if expect.suitableErr != "" {
  1236  						tCtx.Fatalf("did not get expected nodeIsSuitable error: %v", expect.suitableErr)
  1237  					}
  1238  					assert.Equal(tCtx, expect.isSuitable, isSuitable, "is suitable")
  1239  
  1240  					driverName, allocation, err := controller.allocate(tCtx, nodeName, tc.resources)
  1241  					if err != nil {
  1242  						if expect.allocateErr == "" {
  1243  							tCtx.Fatalf("unexpected allocate error: %v", err)
  1244  						}
  1245  						require.Equal(tCtx, expect.allocateErr, err.Error())
  1246  						return
  1247  					}
  1248  					if expect.allocateErr != "" {
  1249  						tCtx.Fatalf("did not get expected allocate error: %v", expect.allocateErr)
  1250  					}
  1251  					assert.Equal(tCtx, expect.driverName, driverName, "driver name")
  1252  					assert.Equal(tCtx, expect.allocation, allocation)
  1253  				})
  1254  			}
  1255  		})
  1256  	}
  1257  }