sigs.k8s.io/kueue@v0.6.2/pkg/scheduler/flavorassigner/flavorassigner_test.go (about)

     1  /*
     2  Copyright 2022 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 flavorassigner
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	"github.com/go-logr/logr/testr"
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/google/go-cmp/cmp/cmpopts"
    26  	corev1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/api/resource"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  	"k8s.io/utils/ptr"
    31  
    32  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    33  	"sigs.k8s.io/kueue/pkg/cache"
    34  	"sigs.k8s.io/kueue/pkg/features"
    35  	utiltesting "sigs.k8s.io/kueue/pkg/util/testing"
    36  	"sigs.k8s.io/kueue/pkg/workload"
    37  )
    38  
    39  func TestAssignFlavors(t *testing.T) {
    40  	defaultFlavorFungibility := kueue.FlavorFungibility{
    41  		WhenCanBorrow:  kueue.Borrow,
    42  		WhenCanPreempt: kueue.TryNextFlavor,
    43  	}
    44  	resourceFlavors := map[kueue.ResourceFlavorReference]*kueue.ResourceFlavor{
    45  		"default": {
    46  			ObjectMeta: metav1.ObjectMeta{Name: "default"},
    47  		},
    48  		"one":   utiltesting.MakeResourceFlavor("one").Label("type", "one").Obj(),
    49  		"two":   utiltesting.MakeResourceFlavor("two").Label("type", "two").Obj(),
    50  		"b_one": utiltesting.MakeResourceFlavor("b_one").Label("b_type", "one").Obj(),
    51  		"b_two": utiltesting.MakeResourceFlavor("b_two").Label("b_type", "two").Obj(),
    52  		"tainted": utiltesting.MakeResourceFlavor("tainted").
    53  			Taint(corev1.Taint{
    54  				Key:    "instance",
    55  				Value:  "spot",
    56  				Effect: corev1.TaintEffectNoSchedule,
    57  			}).Obj(),
    58  	}
    59  
    60  	cases := map[string]struct {
    61  		wlPods             []kueue.PodSet
    62  		wlReclaimablePods  []kueue.ReclaimablePod
    63  		clusterQueue       cache.ClusterQueue
    64  		wantRepMode        FlavorAssignmentMode
    65  		wantAssignment     Assignment
    66  		enableLendingLimit bool
    67  	}{
    68  		"single flavor, fits": {
    69  			wlPods: []kueue.PodSet{
    70  				*utiltesting.MakePodSet("main", 1).
    71  					Request(corev1.ResourceCPU, "1").
    72  					Request(corev1.ResourceMemory, "1Mi").
    73  					Obj(),
    74  			},
    75  			clusterQueue: cache.ClusterQueue{
    76  				ResourceGroups: []cache.ResourceGroup{{
    77  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourceMemory),
    78  					Flavors: []cache.FlavorQuotas{{
    79  						Name: "default",
    80  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
    81  							corev1.ResourceCPU:    {Nominal: 1000},
    82  							corev1.ResourceMemory: {Nominal: 2 * utiltesting.Mi},
    83  						},
    84  					}},
    85  				}},
    86  			},
    87  			wantRepMode: Fit,
    88  			wantAssignment: Assignment{
    89  				PodSets: []PodSetAssignment{
    90  					{
    91  						Name: "main",
    92  						Flavors: ResourceAssignment{
    93  							corev1.ResourceCPU:    {Name: "default", Mode: Fit},
    94  							corev1.ResourceMemory: {Name: "default", Mode: Fit},
    95  						},
    96  						Requests: corev1.ResourceList{
    97  							corev1.ResourceCPU:    resource.MustParse("1000m"),
    98  							corev1.ResourceMemory: resource.MustParse("1Mi"),
    99  						},
   100  						Count: 1,
   101  					},
   102  				},
   103  				Usage: cache.FlavorResourceQuantities{
   104  					"default": map[corev1.ResourceName]int64{
   105  						corev1.ResourceCPU:    1000,
   106  						corev1.ResourceMemory: 1 * 1024 * 1024,
   107  					},
   108  				},
   109  			},
   110  		},
   111  		"single flavor, fits tainted flavor": {
   112  			wlPods: []kueue.PodSet{
   113  				*utiltesting.MakePodSet("main", 1).
   114  					Request(corev1.ResourceCPU, "1").
   115  					Toleration(corev1.Toleration{
   116  						Key:      "instance",
   117  						Operator: corev1.TolerationOpEqual,
   118  						Value:    "spot",
   119  						Effect:   corev1.TaintEffectNoSchedule,
   120  					}).
   121  					Obj(),
   122  			},
   123  			clusterQueue: cache.ClusterQueue{
   124  				ResourceGroups: []cache.ResourceGroup{{
   125  					CoveredResources: sets.New(corev1.ResourceCPU),
   126  					Flavors: []cache.FlavorQuotas{{
   127  						Name: "tainted",
   128  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   129  							corev1.ResourceCPU: {Nominal: 4000},
   130  						},
   131  					}},
   132  				}},
   133  			},
   134  			wantRepMode: Fit,
   135  			wantAssignment: Assignment{
   136  				PodSets: []PodSetAssignment{{
   137  					Name: "main",
   138  					Flavors: ResourceAssignment{
   139  						corev1.ResourceCPU: {Name: "tainted", Mode: Fit},
   140  					},
   141  					Requests: corev1.ResourceList{
   142  						corev1.ResourceCPU: resource.MustParse("1000m"),
   143  					},
   144  					Count: 1,
   145  				}},
   146  				Usage: cache.FlavorResourceQuantities{
   147  					"tainted": {
   148  						corev1.ResourceCPU: 1000,
   149  					},
   150  				},
   151  			},
   152  		},
   153  		"single flavor, used resources, doesn't fit": {
   154  			wlPods: []kueue.PodSet{
   155  				*utiltesting.MakePodSet("main", 1).
   156  					Request(corev1.ResourceCPU, "2").
   157  					Obj(),
   158  			},
   159  			clusterQueue: cache.ClusterQueue{
   160  				ResourceGroups: []cache.ResourceGroup{{
   161  					CoveredResources: sets.New(corev1.ResourceCPU),
   162  					Flavors: []cache.FlavorQuotas{{
   163  						Name: "default",
   164  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   165  							corev1.ResourceCPU: {Nominal: 4000},
   166  						},
   167  					}},
   168  				}},
   169  				Usage: cache.FlavorResourceQuantities{
   170  					"default": {corev1.ResourceCPU: 3_000},
   171  				},
   172  			},
   173  			wantRepMode: Preempt,
   174  			wantAssignment: Assignment{
   175  				PodSets: []PodSetAssignment{{
   176  					Name: "main",
   177  					Flavors: ResourceAssignment{
   178  						corev1.ResourceCPU: {Name: "default", Mode: Preempt},
   179  					},
   180  					Requests: corev1.ResourceList{
   181  						corev1.ResourceCPU: resource.MustParse("2000m"),
   182  					},
   183  					Status: &Status{
   184  						reasons: []string{"insufficient unused quota for cpu in flavor default, 1 more needed"},
   185  					},
   186  					Count: 1,
   187  				}},
   188  				Usage: cache.FlavorResourceQuantities{
   189  					"default": {
   190  						corev1.ResourceCPU: 2000,
   191  					},
   192  				},
   193  			},
   194  		},
   195  		"multiple resource groups, fits": {
   196  			wlPods: []kueue.PodSet{
   197  				*utiltesting.MakePodSet("main", 1).
   198  					Request(corev1.ResourceCPU, "3").
   199  					Request(corev1.ResourceMemory, "10Mi").
   200  					Obj(),
   201  			},
   202  			clusterQueue: cache.ClusterQueue{
   203  				ResourceGroups: []cache.ResourceGroup{
   204  					{
   205  						CoveredResources: sets.New(corev1.ResourceCPU),
   206  						Flavors: []cache.FlavorQuotas{
   207  							{
   208  								Name: "one",
   209  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   210  									corev1.ResourceCPU: {Nominal: 2000},
   211  								},
   212  							},
   213  							{
   214  								Name: "two",
   215  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   216  									corev1.ResourceCPU: {Nominal: 4000},
   217  								},
   218  							},
   219  						},
   220  					},
   221  					{
   222  						CoveredResources: sets.New(corev1.ResourceMemory),
   223  						Flavors: []cache.FlavorQuotas{
   224  							{
   225  								Name: "b_one",
   226  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   227  									corev1.ResourceMemory: {Nominal: utiltesting.Gi},
   228  								},
   229  							},
   230  							{
   231  								Name: "b_two",
   232  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   233  									corev1.ResourceMemory: {Nominal: 5 * utiltesting.Gi},
   234  								},
   235  							},
   236  						},
   237  					},
   238  				},
   239  			},
   240  			wantRepMode: Fit,
   241  			wantAssignment: Assignment{
   242  				PodSets: []PodSetAssignment{{
   243  					Name: "main",
   244  					Flavors: ResourceAssignment{
   245  						corev1.ResourceCPU:    {Name: "two", Mode: Fit},
   246  						corev1.ResourceMemory: {Name: "b_one", Mode: Fit},
   247  					},
   248  					Requests: corev1.ResourceList{
   249  						corev1.ResourceCPU:    resource.MustParse("3000m"),
   250  						corev1.ResourceMemory: resource.MustParse("10Mi"),
   251  					},
   252  					Count: 1,
   253  				}},
   254  				Usage: cache.FlavorResourceQuantities{
   255  					"two": map[corev1.ResourceName]int64{
   256  						corev1.ResourceCPU: 3000,
   257  					},
   258  					"b_one": map[corev1.ResourceName]int64{
   259  						corev1.ResourceMemory: 10 * 1024 * 1024,
   260  					},
   261  				},
   262  			},
   263  		},
   264  		"multiple resource groups, one could fit with preemption, other doesn't fit": {
   265  			wlPods: []kueue.PodSet{
   266  				*utiltesting.MakePodSet("main", 1).
   267  					Request(corev1.ResourceCPU, "3").
   268  					Request(corev1.ResourceMemory, "10Mi").
   269  					Obj(),
   270  			},
   271  			clusterQueue: cache.ClusterQueue{
   272  				ResourceGroups: []cache.ResourceGroup{
   273  					{
   274  						CoveredResources: sets.New(corev1.ResourceCPU),
   275  						Flavors: []cache.FlavorQuotas{{
   276  							Name: "one",
   277  							Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   278  								corev1.ResourceCPU: {Nominal: 3000},
   279  							},
   280  						}},
   281  					},
   282  					{
   283  						CoveredResources: sets.New(corev1.ResourceMemory),
   284  						Flavors: []cache.FlavorQuotas{{
   285  							Name: "b_one",
   286  							Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   287  								corev1.ResourceMemory: {Nominal: utiltesting.Mi},
   288  							},
   289  						}},
   290  					},
   291  				},
   292  				Usage: cache.FlavorResourceQuantities{
   293  					"one": {corev1.ResourceCPU: 1000},
   294  				},
   295  			},
   296  			wantAssignment: Assignment{
   297  				PodSets: []PodSetAssignment{{
   298  					Name: "main",
   299  					Requests: corev1.ResourceList{
   300  						corev1.ResourceCPU:    resource.MustParse("3000m"),
   301  						corev1.ResourceMemory: resource.MustParse("10Mi"),
   302  					},
   303  					Status: &Status{
   304  						reasons: []string{
   305  							"insufficient quota for memory in flavor b_one in ClusterQueue",
   306  						},
   307  					},
   308  					Count: 1,
   309  				}},
   310  				Usage: cache.FlavorResourceQuantities{},
   311  			},
   312  		},
   313  		"multiple resource groups with multiple resources, fits": {
   314  			wlPods: []kueue.PodSet{
   315  				*utiltesting.MakePodSet("main", 1).
   316  					Request(corev1.ResourceCPU, "3").
   317  					Request(corev1.ResourceMemory, "10Mi").
   318  					Request("example.com/gpu", "3").
   319  					Obj(),
   320  			},
   321  			clusterQueue: cache.ClusterQueue{
   322  				ResourceGroups: []cache.ResourceGroup{
   323  					{
   324  						CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourceMemory),
   325  						Flavors: []cache.FlavorQuotas{
   326  							{
   327  								Name: "one",
   328  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   329  									corev1.ResourceCPU:    {Nominal: 2000},
   330  									corev1.ResourceMemory: {Nominal: utiltesting.Gi},
   331  								},
   332  							},
   333  							{
   334  								Name: "two",
   335  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   336  									corev1.ResourceCPU:    {Nominal: 4000},
   337  									corev1.ResourceMemory: {Nominal: 15 * utiltesting.Mi},
   338  								},
   339  							},
   340  						},
   341  					},
   342  					{
   343  						CoveredResources: sets.New[corev1.ResourceName]("example.com/gpu"),
   344  						Flavors: []cache.FlavorQuotas{
   345  							{
   346  								Name: "b_one",
   347  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   348  									"example.com/gpu": {Nominal: 4},
   349  								},
   350  							},
   351  							{
   352  								Name: "b_two",
   353  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   354  									"example.com/gpu": {Nominal: 2},
   355  								},
   356  							},
   357  						},
   358  					},
   359  				},
   360  			},
   361  			wantRepMode: Fit,
   362  			wantAssignment: Assignment{
   363  				PodSets: []PodSetAssignment{{
   364  					Name: "main",
   365  					Flavors: ResourceAssignment{
   366  						corev1.ResourceCPU:    {Name: "two", Mode: Fit},
   367  						corev1.ResourceMemory: {Name: "two", Mode: Fit},
   368  						"example.com/gpu":     {Name: "b_one", Mode: Fit},
   369  					},
   370  					Requests: corev1.ResourceList{
   371  						corev1.ResourceCPU:    resource.MustParse("3000m"),
   372  						corev1.ResourceMemory: resource.MustParse("10Mi"),
   373  						"example.com/gpu":     resource.MustParse("3"),
   374  					},
   375  					Count: 1,
   376  				}},
   377  				Usage: cache.FlavorResourceQuantities{
   378  					"two": map[corev1.ResourceName]int64{
   379  						corev1.ResourceCPU:    3000,
   380  						corev1.ResourceMemory: 10 * 1024 * 1024,
   381  					},
   382  					"b_one": map[corev1.ResourceName]int64{
   383  						"example.com/gpu": 3,
   384  					},
   385  				},
   386  			},
   387  		},
   388  		"multiple resource groups with multiple resources, fits with different modes": {
   389  			wlPods: []kueue.PodSet{
   390  				*utiltesting.MakePodSet("main", 1).
   391  					Request(corev1.ResourceCPU, "3").
   392  					Request(corev1.ResourceMemory, "10Mi").
   393  					Request("example.com/gpu", "3").
   394  					Obj(),
   395  			},
   396  			clusterQueue: cache.ClusterQueue{
   397  				ResourceGroups: []cache.ResourceGroup{
   398  					{
   399  						CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourceMemory),
   400  						Flavors: []cache.FlavorQuotas{
   401  							{
   402  								Name: "one",
   403  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   404  									corev1.ResourceCPU:    {Nominal: 2000},
   405  									corev1.ResourceMemory: {Nominal: utiltesting.Gi},
   406  								},
   407  							},
   408  							{
   409  								Name: "two",
   410  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   411  									corev1.ResourceCPU:    {Nominal: 4000},
   412  									corev1.ResourceMemory: {Nominal: 15 * utiltesting.Mi},
   413  								},
   414  							},
   415  						},
   416  					},
   417  					{
   418  						CoveredResources: sets.New[corev1.ResourceName]("example.com/gpu"),
   419  						Flavors: []cache.FlavorQuotas{{
   420  							Name: "b_one",
   421  							Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   422  								"example.com/gpu": {Nominal: 4},
   423  							},
   424  						}},
   425  					},
   426  				},
   427  				Usage: cache.FlavorResourceQuantities{
   428  					"two": {corev1.ResourceMemory: 10 * utiltesting.Mi},
   429  				},
   430  				Cohort: &cache.Cohort{
   431  					RequestableResources: cache.FlavorResourceQuantities{
   432  						"one": {
   433  							corev1.ResourceCPU:    2000,
   434  							corev1.ResourceMemory: utiltesting.Gi,
   435  						},
   436  						"two": {
   437  							corev1.ResourceCPU:    4000,
   438  							corev1.ResourceMemory: 15 * utiltesting.Mi,
   439  						},
   440  						"b_one": {
   441  							"example.com/gpu": 4,
   442  						},
   443  					},
   444  					Usage: cache.FlavorResourceQuantities{
   445  						"two": {
   446  							corev1.ResourceMemory: 10 * utiltesting.Mi,
   447  						},
   448  						"b_one": {
   449  							"example.com/gpu": 2,
   450  						},
   451  					},
   452  				},
   453  			},
   454  			wantRepMode: Preempt,
   455  			wantAssignment: Assignment{
   456  				PodSets: []PodSetAssignment{{
   457  					Name: "main",
   458  					Flavors: ResourceAssignment{
   459  						corev1.ResourceCPU:    {Name: "two", Mode: Fit},
   460  						corev1.ResourceMemory: {Name: "two", Mode: Preempt},
   461  						"example.com/gpu":     {Name: "b_one", Mode: Preempt},
   462  					},
   463  					Requests: corev1.ResourceList{
   464  						corev1.ResourceCPU:    resource.MustParse("3000m"),
   465  						corev1.ResourceMemory: resource.MustParse("10Mi"),
   466  						"example.com/gpu":     resource.MustParse("3"),
   467  					},
   468  					Status: &Status{
   469  						reasons: []string{
   470  							"insufficient unused quota in cohort for cpu in flavor one, 1 more needed",
   471  							"insufficient unused quota in cohort for memory in flavor two, 5Mi more needed",
   472  							"insufficient unused quota in cohort for example.com/gpu in flavor b_one, 1 more needed",
   473  						},
   474  					},
   475  					Count: 1,
   476  				}},
   477  				Usage: cache.FlavorResourceQuantities{
   478  					"two": map[corev1.ResourceName]int64{
   479  						corev1.ResourceCPU:    3000,
   480  						corev1.ResourceMemory: 10 * 1024 * 1024,
   481  					},
   482  					"b_one": map[corev1.ResourceName]int64{
   483  						"example.com/gpu": 3,
   484  					},
   485  				},
   486  			},
   487  		},
   488  		"multiple resources in a group, doesn't fit": {
   489  			wlPods: []kueue.PodSet{
   490  				*utiltesting.MakePodSet("main", 1).
   491  					Request(corev1.ResourceCPU, "3").
   492  					Request(corev1.ResourceMemory, "10Mi").
   493  					Obj(),
   494  			},
   495  			clusterQueue: cache.ClusterQueue{
   496  				ResourceGroups: []cache.ResourceGroup{
   497  					{
   498  						CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourceMemory),
   499  						Flavors: []cache.FlavorQuotas{
   500  							{
   501  								Name: "one",
   502  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   503  									corev1.ResourceCPU:    {Nominal: 2000},
   504  									corev1.ResourceMemory: {Nominal: utiltesting.Gi},
   505  								},
   506  							},
   507  							{
   508  								Name: "two",
   509  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   510  									corev1.ResourceCPU:    {Nominal: 4000},
   511  									corev1.ResourceMemory: {Nominal: 5 * utiltesting.Mi},
   512  								},
   513  							},
   514  						},
   515  					},
   516  				},
   517  			},
   518  			wantAssignment: Assignment{
   519  				PodSets: []PodSetAssignment{{
   520  					Name: "main",
   521  					Requests: corev1.ResourceList{
   522  						corev1.ResourceCPU:    resource.MustParse("3000m"),
   523  						corev1.ResourceMemory: resource.MustParse("10Mi"),
   524  					},
   525  					Status: &Status{
   526  						reasons: []string{
   527  							"insufficient quota for cpu in flavor one in ClusterQueue",
   528  							"insufficient quota for memory in flavor two in ClusterQueue",
   529  						},
   530  					},
   531  					Count: 1,
   532  				}},
   533  				Usage: cache.FlavorResourceQuantities{},
   534  			},
   535  		},
   536  		"multiple flavors, fits while skipping tainted flavor": {
   537  			wlPods: []kueue.PodSet{
   538  				*utiltesting.MakePodSet("main", 1).
   539  					Request(corev1.ResourceCPU, "3").
   540  					Obj(),
   541  			},
   542  			clusterQueue: cache.ClusterQueue{
   543  				ResourceGroups: []cache.ResourceGroup{
   544  					{
   545  						CoveredResources: sets.New(corev1.ResourceCPU),
   546  						Flavors: []cache.FlavorQuotas{
   547  							{
   548  								Name: "tainted",
   549  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   550  									corev1.ResourceCPU: {Nominal: 4000},
   551  								},
   552  							},
   553  							{
   554  								Name: "two",
   555  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   556  									corev1.ResourceCPU: {Nominal: 4000},
   557  								},
   558  							},
   559  						},
   560  					},
   561  				},
   562  			},
   563  			wantRepMode: Fit,
   564  			wantAssignment: Assignment{
   565  				PodSets: []PodSetAssignment{{
   566  					Name: "main",
   567  					Flavors: ResourceAssignment{
   568  						corev1.ResourceCPU: {Name: "two", Mode: Fit},
   569  					},
   570  					Requests: corev1.ResourceList{
   571  						corev1.ResourceCPU: resource.MustParse("3000m"),
   572  					},
   573  					Count: 1,
   574  				}},
   575  				Usage: cache.FlavorResourceQuantities{
   576  					"two": map[corev1.ResourceName]int64{
   577  						corev1.ResourceCPU: 3000,
   578  					},
   579  				},
   580  			},
   581  		},
   582  		"multiple flavors, skip missing ResourceFlavor": {
   583  			wlPods: []kueue.PodSet{
   584  				*utiltesting.MakePodSet("main", 1).
   585  					Request(corev1.ResourceCPU, "3").
   586  					Obj(),
   587  			},
   588  			clusterQueue: cache.ClusterQueue{
   589  				ResourceGroups: []cache.ResourceGroup{
   590  					{
   591  						CoveredResources: sets.New(corev1.ResourceCPU),
   592  						Flavors: []cache.FlavorQuotas{
   593  							{
   594  								Name: "nonexistent-flavor",
   595  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   596  									corev1.ResourceCPU: {Nominal: 4000},
   597  								},
   598  							},
   599  							{
   600  								Name: "two",
   601  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   602  									corev1.ResourceCPU: {Nominal: 4000},
   603  								},
   604  							},
   605  						},
   606  					},
   607  				},
   608  			},
   609  			wantRepMode: Fit,
   610  			wantAssignment: Assignment{
   611  				PodSets: []PodSetAssignment{{
   612  					Name: "main",
   613  					Flavors: ResourceAssignment{
   614  						corev1.ResourceCPU: {Name: "two", Mode: Fit},
   615  					},
   616  					Requests: corev1.ResourceList{
   617  						corev1.ResourceCPU: resource.MustParse("3000m"),
   618  					},
   619  					Count: 1,
   620  				}},
   621  				Usage: cache.FlavorResourceQuantities{
   622  					"two": map[corev1.ResourceName]int64{
   623  						corev1.ResourceCPU: 3000,
   624  					},
   625  				},
   626  			},
   627  		},
   628  		"multiple flavors, fits a node selector": {
   629  			wlPods: []kueue.PodSet{
   630  				{
   631  					Count: 1,
   632  					Name:  "main",
   633  					Template: corev1.PodTemplateSpec{
   634  						Spec: corev1.PodSpec{
   635  							Containers: utiltesting.SingleContainerForRequest(map[corev1.ResourceName]string{
   636  								corev1.ResourceCPU: "1",
   637  							}),
   638  							// ignored:foo should get ignored
   639  							NodeSelector: map[string]string{"type": "two", "ignored1": "foo"},
   640  							Affinity: &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{
   641  								RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
   642  									NodeSelectorTerms: []corev1.NodeSelectorTerm{
   643  										{
   644  											MatchExpressions: []corev1.NodeSelectorRequirement{
   645  												{
   646  													// this expression should get ignored
   647  													Key:      "ignored2",
   648  													Operator: corev1.NodeSelectorOpIn,
   649  													Values:   []string{"bar"},
   650  												},
   651  											},
   652  										},
   653  									},
   654  								}},
   655  							},
   656  						},
   657  					},
   658  				},
   659  			},
   660  			clusterQueue: cache.ClusterQueue{
   661  				ResourceGroups: []cache.ResourceGroup{
   662  					{
   663  						CoveredResources: sets.New(corev1.ResourceCPU),
   664  						Flavors: []cache.FlavorQuotas{
   665  							{
   666  								Name: "non-existent",
   667  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   668  									corev1.ResourceCPU: {Nominal: 4000},
   669  								},
   670  							},
   671  							{
   672  								Name: "one",
   673  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   674  									corev1.ResourceCPU: {Nominal: 4000},
   675  								},
   676  							},
   677  							{
   678  								Name: "two",
   679  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   680  									corev1.ResourceCPU: {Nominal: 4000},
   681  								},
   682  							},
   683  						},
   684  					},
   685  				},
   686  			},
   687  			wantRepMode: Fit,
   688  			wantAssignment: Assignment{
   689  				PodSets: []PodSetAssignment{{
   690  					Name: "main",
   691  					Flavors: ResourceAssignment{
   692  						corev1.ResourceCPU: {Name: "two", Mode: Fit},
   693  					},
   694  					Requests: corev1.ResourceList{
   695  						corev1.ResourceCPU: resource.MustParse("1000m"),
   696  					},
   697  					Count: 1,
   698  				}},
   699  				Usage: cache.FlavorResourceQuantities{
   700  					"two": map[corev1.ResourceName]int64{
   701  						corev1.ResourceCPU: 1000,
   702  					},
   703  				},
   704  			},
   705  		},
   706  		"multiple flavors, fits with node affinity": {
   707  			wlPods: []kueue.PodSet{
   708  				{
   709  					Count: 1,
   710  					Name:  "main",
   711  					Template: corev1.PodTemplateSpec{
   712  						Spec: corev1.PodSpec{
   713  							Containers: utiltesting.SingleContainerForRequest(map[corev1.ResourceName]string{
   714  								corev1.ResourceCPU:    "1",
   715  								corev1.ResourceMemory: "1Mi",
   716  							}),
   717  							NodeSelector: map[string]string{"ignored1": "foo"},
   718  							Affinity: &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{
   719  								RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
   720  									NodeSelectorTerms: []corev1.NodeSelectorTerm{
   721  										{
   722  											MatchExpressions: []corev1.NodeSelectorRequirement{
   723  												{
   724  													Key:      "type",
   725  													Operator: corev1.NodeSelectorOpIn,
   726  													Values:   []string{"two"},
   727  												},
   728  											},
   729  										},
   730  									},
   731  								}},
   732  							},
   733  						},
   734  					},
   735  				},
   736  			},
   737  			clusterQueue: cache.ClusterQueue{
   738  				ResourceGroups: []cache.ResourceGroup{
   739  					{
   740  						CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourceMemory),
   741  						Flavors: []cache.FlavorQuotas{
   742  							{
   743  								Name: "one",
   744  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   745  									corev1.ResourceCPU:    {Nominal: 4000},
   746  									corev1.ResourceMemory: {Nominal: utiltesting.Gi},
   747  								},
   748  							},
   749  							{
   750  								Name: "two",
   751  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   752  									corev1.ResourceCPU:    {Nominal: 4000},
   753  									corev1.ResourceMemory: {Nominal: utiltesting.Gi},
   754  								},
   755  							},
   756  						},
   757  					},
   758  				},
   759  			},
   760  			wantRepMode: Fit,
   761  			wantAssignment: Assignment{
   762  				PodSets: []PodSetAssignment{{
   763  					Name: "main",
   764  					Flavors: ResourceAssignment{
   765  						corev1.ResourceCPU:    {Name: "two", Mode: Fit},
   766  						corev1.ResourceMemory: {Name: "two", Mode: Fit},
   767  					},
   768  					Requests: corev1.ResourceList{
   769  						corev1.ResourceCPU:    resource.MustParse("1000m"),
   770  						corev1.ResourceMemory: resource.MustParse("1Mi"),
   771  					},
   772  					Count: 1,
   773  				}},
   774  				Usage: cache.FlavorResourceQuantities{
   775  					"two": map[corev1.ResourceName]int64{
   776  						corev1.ResourceCPU:    1000,
   777  						corev1.ResourceMemory: 1 * 1024 * 1024,
   778  					},
   779  				},
   780  			},
   781  		},
   782  		"multiple flavors, node affinity fits any flavor": {
   783  			wlPods: []kueue.PodSet{
   784  				{
   785  					Count: 1,
   786  					Name:  "main",
   787  					Template: corev1.PodTemplateSpec{
   788  						Spec: corev1.PodSpec{
   789  							Containers: utiltesting.SingleContainerForRequest(map[corev1.ResourceName]string{
   790  								corev1.ResourceCPU: "1",
   791  							}),
   792  							Affinity: &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{
   793  								RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
   794  									NodeSelectorTerms: []corev1.NodeSelectorTerm{
   795  										{
   796  											MatchExpressions: []corev1.NodeSelectorRequirement{
   797  												{
   798  													Key:      "ignored2",
   799  													Operator: corev1.NodeSelectorOpIn,
   800  													Values:   []string{"bar"},
   801  												},
   802  											},
   803  										},
   804  										{
   805  											MatchExpressions: []corev1.NodeSelectorRequirement{
   806  												{
   807  													// although this terms selects two
   808  													// the first term practically matches
   809  													// any flavor; and since the terms
   810  													// are ORed, any flavor can be selected.
   811  													Key:      "cpuType",
   812  													Operator: corev1.NodeSelectorOpIn,
   813  													Values:   []string{"two"},
   814  												},
   815  											},
   816  										},
   817  									},
   818  								}},
   819  							},
   820  						},
   821  					},
   822  				},
   823  			},
   824  			clusterQueue: cache.ClusterQueue{
   825  				ResourceGroups: []cache.ResourceGroup{
   826  					{
   827  						CoveredResources: sets.New(corev1.ResourceCPU),
   828  						Flavors: []cache.FlavorQuotas{
   829  							{
   830  								Name: "one",
   831  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   832  									corev1.ResourceCPU: {Nominal: 4000},
   833  								},
   834  							},
   835  							{
   836  								Name: "two",
   837  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   838  									corev1.ResourceCPU: {Nominal: 4000},
   839  								},
   840  							},
   841  						},
   842  					},
   843  				},
   844  			},
   845  			wantRepMode: Fit,
   846  			wantAssignment: Assignment{
   847  				PodSets: []PodSetAssignment{{
   848  					Name: "main",
   849  					Flavors: ResourceAssignment{
   850  						corev1.ResourceCPU: {Name: "one", Mode: Fit},
   851  					},
   852  					Requests: corev1.ResourceList{
   853  						corev1.ResourceCPU: resource.MustParse("1000m"),
   854  					},
   855  					Count: 1,
   856  				}},
   857  				Usage: cache.FlavorResourceQuantities{
   858  					"one": map[corev1.ResourceName]int64{
   859  						corev1.ResourceCPU: 1000,
   860  					},
   861  				},
   862  			},
   863  		},
   864  		"multiple flavors, doesn't fit node affinity": {
   865  			wlPods: []kueue.PodSet{
   866  				{
   867  					Count: 1,
   868  					Name:  "main",
   869  					Template: corev1.PodTemplateSpec{
   870  						Spec: corev1.PodSpec{
   871  							Containers: utiltesting.SingleContainerForRequest(map[corev1.ResourceName]string{
   872  								corev1.ResourceCPU: "1",
   873  							}),
   874  							Affinity: &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{
   875  								RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
   876  									NodeSelectorTerms: []corev1.NodeSelectorTerm{
   877  										{
   878  											MatchExpressions: []corev1.NodeSelectorRequirement{
   879  												{
   880  													Key:      "type",
   881  													Operator: corev1.NodeSelectorOpIn,
   882  													Values:   []string{"three"},
   883  												},
   884  											},
   885  										},
   886  									},
   887  								}},
   888  							},
   889  						},
   890  					},
   891  				},
   892  			},
   893  			clusterQueue: cache.ClusterQueue{
   894  				ResourceGroups: []cache.ResourceGroup{
   895  					{
   896  						CoveredResources: sets.New(corev1.ResourceCPU),
   897  						Flavors: []cache.FlavorQuotas{
   898  							{
   899  								Name: "one",
   900  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   901  									corev1.ResourceCPU: {Nominal: 4000},
   902  								},
   903  							},
   904  							{
   905  								Name: "two",
   906  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   907  									corev1.ResourceCPU: {Nominal: 4000},
   908  								},
   909  							},
   910  						},
   911  					},
   912  				},
   913  			},
   914  			wantAssignment: Assignment{
   915  				PodSets: []PodSetAssignment{{
   916  					Name: "main",
   917  					Requests: corev1.ResourceList{
   918  						corev1.ResourceCPU: resource.MustParse("1000m"),
   919  					},
   920  					Status: &Status{
   921  						reasons: []string{
   922  							"flavor one doesn't match node affinity",
   923  							"flavor two doesn't match node affinity",
   924  						},
   925  					},
   926  					Count: 1,
   927  				}},
   928  				Usage: cache.FlavorResourceQuantities{},
   929  			},
   930  		},
   931  		"multiple specs, fit different flavors": {
   932  			wlPods: []kueue.PodSet{
   933  				*utiltesting.MakePodSet("driver", 1).
   934  					Request(corev1.ResourceCPU, "5").
   935  					Obj(),
   936  				*utiltesting.MakePodSet("worker", 1).
   937  					Request(corev1.ResourceCPU, "3").
   938  					Obj(),
   939  			},
   940  			clusterQueue: cache.ClusterQueue{
   941  				ResourceGroups: []cache.ResourceGroup{
   942  					{
   943  						CoveredResources: sets.New(corev1.ResourceCPU),
   944  						Flavors: []cache.FlavorQuotas{
   945  							{
   946  								Name: "one",
   947  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   948  									corev1.ResourceCPU: {Nominal: 4000},
   949  								},
   950  							},
   951  							{
   952  								Name: "two",
   953  								Resources: map[corev1.ResourceName]*cache.ResourceQuota{
   954  									corev1.ResourceCPU: {Nominal: 10_000},
   955  								},
   956  							},
   957  						},
   958  					},
   959  				},
   960  			},
   961  			wantRepMode: Fit,
   962  			wantAssignment: Assignment{
   963  				PodSets: []PodSetAssignment{
   964  					{
   965  						Name: "driver",
   966  						Flavors: ResourceAssignment{
   967  							corev1.ResourceCPU: {Name: "two", Mode: Fit},
   968  						},
   969  						Requests: corev1.ResourceList{
   970  							corev1.ResourceCPU: resource.MustParse("5000m"),
   971  						},
   972  						Count: 1,
   973  					},
   974  					{
   975  						Name: "worker",
   976  						Flavors: ResourceAssignment{
   977  							corev1.ResourceCPU: {Name: "one", Mode: Fit},
   978  						},
   979  						Requests: corev1.ResourceList{
   980  							corev1.ResourceCPU: resource.MustParse("3000m"),
   981  						},
   982  						Count: 1,
   983  					},
   984  				},
   985  				Usage: cache.FlavorResourceQuantities{
   986  					"one": map[corev1.ResourceName]int64{
   987  						corev1.ResourceCPU: 3000,
   988  					},
   989  					"two": map[corev1.ResourceName]int64{
   990  						corev1.ResourceCPU: 5000,
   991  					},
   992  				},
   993  			},
   994  		},
   995  		"multiple specs, fits borrowing": {
   996  			wlPods: []kueue.PodSet{
   997  				*utiltesting.MakePodSet("driver", 1).
   998  					Request(corev1.ResourceCPU, "4").
   999  					Request(corev1.ResourceMemory, "1Gi").
  1000  					Obj(),
  1001  				*utiltesting.MakePodSet("worker", 1).
  1002  					Request(corev1.ResourceCPU, "6").
  1003  					Request(corev1.ResourceMemory, "4Gi").
  1004  					Obj(),
  1005  			},
  1006  			clusterQueue: cache.ClusterQueue{
  1007  				ResourceGroups: []cache.ResourceGroup{{
  1008  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourceMemory),
  1009  					Flavors: []cache.FlavorQuotas{{
  1010  						Name: "default",
  1011  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1012  							corev1.ResourceCPU:    {Nominal: 2000, BorrowingLimit: ptr.To[int64](98_000)},
  1013  							corev1.ResourceMemory: {Nominal: 2 * utiltesting.Gi},
  1014  						},
  1015  					}},
  1016  				}},
  1017  				Cohort: &cache.Cohort{
  1018  					RequestableResources: cache.FlavorResourceQuantities{
  1019  						"default": {
  1020  							corev1.ResourceCPU:    200_000,
  1021  							corev1.ResourceMemory: 200 * utiltesting.Gi,
  1022  						},
  1023  					},
  1024  				},
  1025  			},
  1026  			wantRepMode: Fit,
  1027  			wantAssignment: Assignment{
  1028  				PodSets: []PodSetAssignment{
  1029  					{
  1030  						Name: "driver",
  1031  						Flavors: ResourceAssignment{
  1032  							corev1.ResourceCPU:    {Name: "default", Mode: Fit},
  1033  							corev1.ResourceMemory: {Name: "default", Mode: Fit},
  1034  						},
  1035  						Requests: corev1.ResourceList{
  1036  							corev1.ResourceCPU:    resource.MustParse("4000m"),
  1037  							corev1.ResourceMemory: resource.MustParse("1Gi"),
  1038  						},
  1039  						Count: 1,
  1040  					},
  1041  					{
  1042  						Name: "worker",
  1043  						Flavors: ResourceAssignment{
  1044  							corev1.ResourceCPU:    {Name: "default", Mode: Fit},
  1045  							corev1.ResourceMemory: {Name: "default", Mode: Fit},
  1046  						},
  1047  						Requests: corev1.ResourceList{
  1048  							corev1.ResourceCPU:    resource.MustParse("6000m"),
  1049  							corev1.ResourceMemory: resource.MustParse("4Gi"),
  1050  						},
  1051  						Count: 1,
  1052  					},
  1053  				},
  1054  				Borrowing: true,
  1055  				Usage: cache.FlavorResourceQuantities{
  1056  					"default": map[corev1.ResourceName]int64{
  1057  						corev1.ResourceCPU:    10000,
  1058  						corev1.ResourceMemory: 5 * 1024 * 1024 * 1024,
  1059  					},
  1060  				},
  1061  			},
  1062  		},
  1063  		"not enough space to borrow": {
  1064  			wlPods: []kueue.PodSet{
  1065  				*utiltesting.MakePodSet("main", 1).
  1066  					Request(corev1.ResourceCPU, "2").
  1067  					Obj(),
  1068  			},
  1069  			clusterQueue: cache.ClusterQueue{
  1070  				ResourceGroups: []cache.ResourceGroup{{
  1071  					CoveredResources: sets.New(corev1.ResourceCPU),
  1072  					Flavors: []cache.FlavorQuotas{{
  1073  						Name: "one",
  1074  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1075  							corev1.ResourceCPU: {Nominal: 1000},
  1076  						},
  1077  					}},
  1078  				}},
  1079  				Cohort: &cache.Cohort{
  1080  					RequestableResources: cache.FlavorResourceQuantities{
  1081  						"one": {corev1.ResourceCPU: 10_000},
  1082  					},
  1083  					Usage: cache.FlavorResourceQuantities{
  1084  						"one": {corev1.ResourceCPU: 9_000},
  1085  					},
  1086  				},
  1087  			},
  1088  			wantAssignment: Assignment{
  1089  				PodSets: []PodSetAssignment{{
  1090  					Name: "main",
  1091  					Requests: corev1.ResourceList{
  1092  						corev1.ResourceCPU: resource.MustParse("2000m"),
  1093  					},
  1094  					Status: &Status{
  1095  						reasons: []string{"insufficient unused quota in cohort for cpu in flavor one, 1 more needed"},
  1096  					},
  1097  					Count: 1,
  1098  				}},
  1099  				Usage: cache.FlavorResourceQuantities{},
  1100  			},
  1101  		},
  1102  		"past max, but can preempt in ClusterQueue": {
  1103  			wlPods: []kueue.PodSet{
  1104  				*utiltesting.MakePodSet("main", 1).
  1105  					Request(corev1.ResourceCPU, "2").
  1106  					Obj(),
  1107  			},
  1108  			clusterQueue: cache.ClusterQueue{
  1109  				ResourceGroups: []cache.ResourceGroup{{
  1110  					CoveredResources: sets.New(corev1.ResourceCPU),
  1111  					Flavors: []cache.FlavorQuotas{{
  1112  						Name: "one",
  1113  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1114  							corev1.ResourceCPU: {Nominal: 2000, BorrowingLimit: ptr.To[int64](8_000)},
  1115  						},
  1116  					}},
  1117  				}},
  1118  				Usage: cache.FlavorResourceQuantities{
  1119  					"one": {corev1.ResourceCPU: 9_000},
  1120  				},
  1121  				Cohort: &cache.Cohort{
  1122  					RequestableResources: cache.FlavorResourceQuantities{
  1123  						"one": {corev1.ResourceCPU: 100_000},
  1124  					},
  1125  					Usage: cache.FlavorResourceQuantities{
  1126  						"one": {corev1.ResourceCPU: 9_000},
  1127  					},
  1128  				},
  1129  			},
  1130  			wantRepMode: Preempt,
  1131  			wantAssignment: Assignment{
  1132  				PodSets: []PodSetAssignment{{
  1133  					Name: "main",
  1134  					Flavors: ResourceAssignment{
  1135  						corev1.ResourceCPU: {Name: "one", Mode: Preempt},
  1136  					},
  1137  					Requests: corev1.ResourceList{
  1138  						corev1.ResourceCPU: resource.MustParse("2000m"),
  1139  					},
  1140  
  1141  					Status: &Status{
  1142  						reasons: []string{"borrowing limit for cpu in flavor one exceeded"},
  1143  					},
  1144  					Count: 1,
  1145  				}},
  1146  				Usage: cache.FlavorResourceQuantities{
  1147  					"one": map[corev1.ResourceName]int64{
  1148  						corev1.ResourceCPU: 2000,
  1149  					},
  1150  				},
  1151  			},
  1152  		},
  1153  		"past min, but can preempt in ClusterQueue": {
  1154  			wlPods: []kueue.PodSet{
  1155  				*utiltesting.MakePodSet("main", 1).
  1156  					Request(corev1.ResourceCPU, "2").
  1157  					Obj(),
  1158  			},
  1159  			clusterQueue: cache.ClusterQueue{
  1160  				ResourceGroups: []cache.ResourceGroup{{
  1161  					CoveredResources: sets.New(corev1.ResourceCPU),
  1162  					Flavors: []cache.FlavorQuotas{{
  1163  						Name: "one",
  1164  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1165  							corev1.ResourceCPU: {Nominal: 2000},
  1166  						},
  1167  					}},
  1168  				}},
  1169  				Usage: cache.FlavorResourceQuantities{
  1170  					"one": {corev1.ResourceCPU: 1_000},
  1171  				},
  1172  			},
  1173  			wantRepMode: Preempt,
  1174  			wantAssignment: Assignment{
  1175  				PodSets: []PodSetAssignment{{
  1176  					Name: "main",
  1177  					Flavors: ResourceAssignment{
  1178  						corev1.ResourceCPU: {Name: "one", Mode: Preempt},
  1179  					},
  1180  					Requests: corev1.ResourceList{
  1181  						corev1.ResourceCPU: resource.MustParse("2000m"),
  1182  					},
  1183  					Status: &Status{
  1184  						reasons: []string{"insufficient unused quota for cpu in flavor one, 1 more needed"},
  1185  					},
  1186  					Count: 1,
  1187  				}},
  1188  				Usage: cache.FlavorResourceQuantities{
  1189  					"one": map[corev1.ResourceName]int64{
  1190  						corev1.ResourceCPU: 2000,
  1191  					},
  1192  				},
  1193  			},
  1194  		},
  1195  		"past min, but can preempt in cohort and ClusterQueue": {
  1196  			wlPods: []kueue.PodSet{
  1197  				*utiltesting.MakePodSet("main", 1).
  1198  					Request(corev1.ResourceCPU, "2").
  1199  					Obj(),
  1200  			},
  1201  			clusterQueue: cache.ClusterQueue{
  1202  				ResourceGroups: []cache.ResourceGroup{{
  1203  					CoveredResources: sets.New(corev1.ResourceCPU),
  1204  					Flavors: []cache.FlavorQuotas{{
  1205  						Name: "one",
  1206  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1207  							corev1.ResourceCPU: {Nominal: 3000},
  1208  						},
  1209  					}},
  1210  				}},
  1211  				Usage: cache.FlavorResourceQuantities{
  1212  					"one": {corev1.ResourceCPU: 2_000},
  1213  				},
  1214  				Cohort: &cache.Cohort{
  1215  					RequestableResources: cache.FlavorResourceQuantities{
  1216  						"one": {corev1.ResourceCPU: 10_000},
  1217  					},
  1218  					Usage: cache.FlavorResourceQuantities{
  1219  						"one": {corev1.ResourceCPU: 10_000},
  1220  					},
  1221  				},
  1222  			},
  1223  			wantRepMode: Preempt,
  1224  			wantAssignment: Assignment{
  1225  				PodSets: []PodSetAssignment{{
  1226  					Name: "main",
  1227  					Flavors: ResourceAssignment{
  1228  						corev1.ResourceCPU: {Name: "one", Mode: Preempt},
  1229  					},
  1230  					Requests: corev1.ResourceList{
  1231  						corev1.ResourceCPU: resource.MustParse("2000m"),
  1232  					},
  1233  					Status: &Status{
  1234  						reasons: []string{"insufficient unused quota in cohort for cpu in flavor one, 2 more needed"},
  1235  					},
  1236  					Count: 1,
  1237  				}},
  1238  				Usage: cache.FlavorResourceQuantities{
  1239  					"one": map[corev1.ResourceName]int64{
  1240  						corev1.ResourceCPU: 2000,
  1241  					},
  1242  				},
  1243  			},
  1244  		},
  1245  		"can only preempt flavors that match affinity": {
  1246  			wlPods: []kueue.PodSet{
  1247  				{
  1248  					Count: 1,
  1249  					Name:  "main",
  1250  					Template: corev1.PodTemplateSpec{
  1251  						Spec: corev1.PodSpec{
  1252  							Containers: utiltesting.SingleContainerForRequest(map[corev1.ResourceName]string{
  1253  								corev1.ResourceCPU: "2",
  1254  							}),
  1255  							NodeSelector: map[string]string{"type": "two"},
  1256  						},
  1257  					},
  1258  				},
  1259  			},
  1260  			clusterQueue: cache.ClusterQueue{
  1261  				ResourceGroups: []cache.ResourceGroup{{
  1262  					CoveredResources: sets.New(corev1.ResourceCPU),
  1263  					Flavors: []cache.FlavorQuotas{
  1264  						{
  1265  							Name: "one",
  1266  							Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1267  								corev1.ResourceCPU: {Nominal: 4000},
  1268  							},
  1269  						},
  1270  						{
  1271  							Name: "two",
  1272  							Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1273  								corev1.ResourceCPU: {Nominal: 4000},
  1274  							},
  1275  						},
  1276  					},
  1277  				}},
  1278  				Usage: cache.FlavorResourceQuantities{
  1279  					"one": {corev1.ResourceCPU: 3000},
  1280  					"two": {corev1.ResourceCPU: 3000},
  1281  				},
  1282  			},
  1283  			wantRepMode: Preempt,
  1284  			wantAssignment: Assignment{
  1285  				PodSets: []PodSetAssignment{{
  1286  					Name: "main",
  1287  					Flavors: ResourceAssignment{
  1288  						corev1.ResourceCPU: {Name: "two", Mode: Preempt},
  1289  					},
  1290  					Requests: corev1.ResourceList{
  1291  						corev1.ResourceCPU: resource.MustParse("2000m"),
  1292  					},
  1293  					Status: &Status{
  1294  						reasons: []string{
  1295  							"flavor one doesn't match node affinity",
  1296  							"insufficient unused quota for cpu in flavor two, 1 more needed",
  1297  						},
  1298  					},
  1299  					Count: 1,
  1300  				}},
  1301  				Usage: cache.FlavorResourceQuantities{
  1302  					"two": map[corev1.ResourceName]int64{
  1303  						corev1.ResourceCPU: 2000,
  1304  					},
  1305  				},
  1306  			},
  1307  		},
  1308  		"each podset requires preemption on a different flavor": {
  1309  			wlPods: []kueue.PodSet{
  1310  				*utiltesting.MakePodSet("launcher", 1).
  1311  					Request(corev1.ResourceCPU, "2").
  1312  					Obj(),
  1313  				*utiltesting.MakePodSet("workers", 10).
  1314  					Request(corev1.ResourceCPU, "1").
  1315  					Toleration(corev1.Toleration{
  1316  						Key:      "instance",
  1317  						Operator: corev1.TolerationOpEqual,
  1318  						Value:    "spot",
  1319  						Effect:   corev1.TaintEffectNoSchedule,
  1320  					}).
  1321  					Obj(),
  1322  			},
  1323  			clusterQueue: cache.ClusterQueue{
  1324  				ResourceGroups: []cache.ResourceGroup{{
  1325  					CoveredResources: sets.New(corev1.ResourceCPU),
  1326  					Flavors: []cache.FlavorQuotas{
  1327  						{
  1328  							Name: "one",
  1329  							Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1330  								corev1.ResourceCPU: {Nominal: 4000},
  1331  							},
  1332  						},
  1333  						{
  1334  							Name: "tainted",
  1335  							Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1336  								corev1.ResourceCPU: {Nominal: 10_000},
  1337  							},
  1338  						},
  1339  					},
  1340  				}},
  1341  				Usage: cache.FlavorResourceQuantities{
  1342  					"one":     {corev1.ResourceCPU: 3000},
  1343  					"tainted": {corev1.ResourceCPU: 3000},
  1344  				},
  1345  			},
  1346  			wantRepMode: Preempt,
  1347  			wantAssignment: Assignment{
  1348  				PodSets: []PodSetAssignment{
  1349  					{
  1350  						Name: "launcher",
  1351  						Flavors: ResourceAssignment{
  1352  							corev1.ResourceCPU: {Name: "one", Mode: Preempt},
  1353  						},
  1354  						Requests: corev1.ResourceList{
  1355  							corev1.ResourceCPU: resource.MustParse("2000m"),
  1356  						},
  1357  						Status: &Status{
  1358  							reasons: []string{
  1359  								"insufficient unused quota for cpu in flavor one, 1 more needed",
  1360  								"untolerated taint {instance spot NoSchedule <nil>} in flavor tainted",
  1361  							},
  1362  						},
  1363  						Count: 1,
  1364  					},
  1365  					{
  1366  						Name: "workers",
  1367  						Flavors: ResourceAssignment{
  1368  							corev1.ResourceCPU: {Name: "tainted", Mode: Preempt},
  1369  						},
  1370  						Requests: corev1.ResourceList{
  1371  							corev1.ResourceCPU: resource.MustParse("10000m"),
  1372  						},
  1373  						Status: &Status{
  1374  							reasons: []string{
  1375  								"insufficient quota for cpu in flavor one in ClusterQueue",
  1376  								"insufficient unused quota for cpu in flavor tainted, 3 more needed",
  1377  							},
  1378  						},
  1379  						Count: 10,
  1380  					},
  1381  				},
  1382  				Usage: cache.FlavorResourceQuantities{
  1383  					"one": map[corev1.ResourceName]int64{
  1384  						corev1.ResourceCPU: 2000,
  1385  					},
  1386  					"tainted": map[corev1.ResourceName]int64{
  1387  						corev1.ResourceCPU: 10000,
  1388  					},
  1389  				},
  1390  			},
  1391  		},
  1392  		"resource not listed in clusterQueue": {
  1393  			wlPods: []kueue.PodSet{
  1394  				*utiltesting.MakePodSet("main", 1).
  1395  					Request("example.com/gpu", "2").
  1396  					Obj(),
  1397  			},
  1398  			clusterQueue: cache.ClusterQueue{
  1399  				ResourceGroups: []cache.ResourceGroup{{
  1400  					CoveredResources: sets.New(corev1.ResourceCPU),
  1401  					Flavors: []cache.FlavorQuotas{{
  1402  						Name: "one",
  1403  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1404  							corev1.ResourceCPU: {Nominal: 4000},
  1405  						},
  1406  					}},
  1407  				}},
  1408  			},
  1409  			wantAssignment: Assignment{
  1410  				PodSets: []PodSetAssignment{{
  1411  					Name: "main",
  1412  					Requests: corev1.ResourceList{
  1413  						"example.com/gpu": resource.MustParse("2"),
  1414  					},
  1415  					Status: &Status{
  1416  						reasons: []string{"resource example.com/gpu unavailable in ClusterQueue"},
  1417  					},
  1418  					Count: 1,
  1419  				}},
  1420  				Usage: cache.FlavorResourceQuantities{},
  1421  			},
  1422  		},
  1423  		"flavor not found": {
  1424  			wlPods: []kueue.PodSet{
  1425  				*utiltesting.MakePodSet("main", 1).
  1426  					Request(corev1.ResourceCPU, "1").
  1427  					Obj(),
  1428  			},
  1429  			clusterQueue: cache.ClusterQueue{
  1430  				ResourceGroups: []cache.ResourceGroup{{
  1431  					CoveredResources: sets.New(corev1.ResourceCPU),
  1432  					Flavors: []cache.FlavorQuotas{{
  1433  						Name: "nonexistent-flavor",
  1434  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1435  							corev1.ResourceCPU: {Nominal: 1000},
  1436  						},
  1437  					}},
  1438  				}},
  1439  			},
  1440  			wantAssignment: Assignment{
  1441  				PodSets: []PodSetAssignment{{
  1442  					Name: "main",
  1443  					Requests: corev1.ResourceList{
  1444  						corev1.ResourceCPU: resource.MustParse("1000m"),
  1445  					},
  1446  					Status: &Status{
  1447  						reasons: []string{"flavor nonexistent-flavor not found"},
  1448  					},
  1449  					Count: 1,
  1450  				}},
  1451  				Usage: cache.FlavorResourceQuantities{},
  1452  			},
  1453  		},
  1454  		"num pods fit": {
  1455  			wlPods: []kueue.PodSet{
  1456  				*utiltesting.MakePodSet("main", 3).
  1457  					Request(corev1.ResourceCPU, "1").
  1458  					Obj(),
  1459  			},
  1460  			clusterQueue: cache.ClusterQueue{
  1461  				ResourceGroups: []cache.ResourceGroup{{
  1462  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  1463  					Flavors: []cache.FlavorQuotas{{
  1464  						Name: "default",
  1465  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1466  							corev1.ResourcePods: {Nominal: 3},
  1467  							corev1.ResourceCPU:  {Nominal: 10000},
  1468  						},
  1469  					}},
  1470  				}},
  1471  			},
  1472  			wantAssignment: Assignment{
  1473  				PodSets: []PodSetAssignment{{
  1474  					Name: "main",
  1475  					Flavors: ResourceAssignment{
  1476  
  1477  						corev1.ResourceCPU:  &FlavorAssignment{Name: "default", Mode: Fit},
  1478  						corev1.ResourcePods: &FlavorAssignment{Name: "default", Mode: Fit},
  1479  					},
  1480  					Requests: corev1.ResourceList{
  1481  						corev1.ResourceCPU:  resource.MustParse("3000m"),
  1482  						corev1.ResourcePods: resource.MustParse("3"),
  1483  					},
  1484  					Count: 3,
  1485  				}},
  1486  				Usage: cache.FlavorResourceQuantities{
  1487  					"default": map[corev1.ResourceName]int64{
  1488  						corev1.ResourcePods: 3,
  1489  						corev1.ResourceCPU:  3000,
  1490  					},
  1491  				},
  1492  			},
  1493  			wantRepMode: Fit,
  1494  		},
  1495  		"num pods don't fit": {
  1496  			wlPods: []kueue.PodSet{
  1497  				*utiltesting.MakePodSet("main", 3).
  1498  					Request(corev1.ResourceCPU, "1").
  1499  					Obj(),
  1500  			},
  1501  			clusterQueue: cache.ClusterQueue{
  1502  				ResourceGroups: []cache.ResourceGroup{{
  1503  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  1504  					Flavors: []cache.FlavorQuotas{{
  1505  						Name: "default",
  1506  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1507  							corev1.ResourcePods: {Nominal: 2},
  1508  							corev1.ResourceCPU:  {Nominal: 10000},
  1509  						},
  1510  					}},
  1511  				}},
  1512  			},
  1513  			wantAssignment: Assignment{
  1514  				PodSets: []PodSetAssignment{{
  1515  					Name: "main",
  1516  					Requests: corev1.ResourceList{
  1517  						corev1.ResourceCPU:  resource.MustParse("3000m"),
  1518  						corev1.ResourcePods: resource.MustParse("3"),
  1519  					},
  1520  					Status: &Status{
  1521  						reasons: []string{fmt.Sprintf("insufficient quota for %s in flavor default in ClusterQueue", corev1.ResourcePods)},
  1522  					},
  1523  					Count: 3,
  1524  				}},
  1525  				Usage: cache.FlavorResourceQuantities{},
  1526  			},
  1527  		},
  1528  		"with reclaimable pods": {
  1529  			wlPods: []kueue.PodSet{
  1530  				*utiltesting.MakePodSet("main", 5).
  1531  					Request(corev1.ResourceCPU, "1").
  1532  					Obj(),
  1533  			},
  1534  			wlReclaimablePods: []kueue.ReclaimablePod{
  1535  				{
  1536  					Name:  "main",
  1537  					Count: 2,
  1538  				},
  1539  			},
  1540  			clusterQueue: cache.ClusterQueue{
  1541  				ResourceGroups: []cache.ResourceGroup{{
  1542  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  1543  					Flavors: []cache.FlavorQuotas{{
  1544  						Name: "default",
  1545  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1546  							corev1.ResourcePods: {Nominal: 3},
  1547  							corev1.ResourceCPU:  {Nominal: 10000},
  1548  						},
  1549  					}},
  1550  				}},
  1551  			},
  1552  			wantAssignment: Assignment{
  1553  				PodSets: []PodSetAssignment{{
  1554  					Name: "main",
  1555  					Flavors: ResourceAssignment{
  1556  
  1557  						corev1.ResourceCPU:  &FlavorAssignment{Name: "default", Mode: Fit},
  1558  						corev1.ResourcePods: &FlavorAssignment{Name: "default", Mode: Fit},
  1559  					},
  1560  					Requests: corev1.ResourceList{
  1561  						corev1.ResourceCPU:  resource.MustParse("3000m"),
  1562  						corev1.ResourcePods: resource.MustParse("3"),
  1563  					},
  1564  					Count: 3,
  1565  				}},
  1566  				Usage: cache.FlavorResourceQuantities{
  1567  					"default": map[corev1.ResourceName]int64{
  1568  						corev1.ResourcePods: 3,
  1569  						corev1.ResourceCPU:  3000,
  1570  					},
  1571  				},
  1572  			},
  1573  			wantRepMode: Fit,
  1574  		},
  1575  		"preempt before try next flavor": {
  1576  			wlPods: []kueue.PodSet{
  1577  				*utiltesting.MakePodSet("main", 1).
  1578  					Request(corev1.ResourceCPU, "9").
  1579  					Obj(),
  1580  			},
  1581  			clusterQueue: cache.ClusterQueue{
  1582  				FlavorFungibility: kueue.FlavorFungibility{
  1583  					WhenCanBorrow:  kueue.Borrow,
  1584  					WhenCanPreempt: kueue.Preempt,
  1585  				},
  1586  				ResourceGroups: []cache.ResourceGroup{{
  1587  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  1588  					Flavors: []cache.FlavorQuotas{{
  1589  						Name: "one",
  1590  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1591  							corev1.ResourcePods: {Nominal: 10},
  1592  							corev1.ResourceCPU:  {Nominal: 10000},
  1593  						},
  1594  					}, {
  1595  						Name: "two",
  1596  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1597  							corev1.ResourcePods: {Nominal: 10},
  1598  							corev1.ResourceCPU:  {Nominal: 10000},
  1599  						},
  1600  					}},
  1601  				}},
  1602  				Usage: cache.FlavorResourceQuantities{
  1603  					"one": {corev1.ResourceCPU: 2000},
  1604  				},
  1605  			},
  1606  			wantRepMode: Preempt,
  1607  			wantAssignment: Assignment{
  1608  				PodSets: []PodSetAssignment{{
  1609  					Name: "main",
  1610  					Flavors: ResourceAssignment{
  1611  						corev1.ResourceCPU:  {Name: "one", Mode: Preempt},
  1612  						corev1.ResourcePods: {Name: "one", Mode: Fit},
  1613  					},
  1614  					Requests: corev1.ResourceList{
  1615  						corev1.ResourceCPU:  resource.MustParse("9000m"),
  1616  						corev1.ResourcePods: resource.MustParse("1"),
  1617  					},
  1618  					Status: &Status{
  1619  						reasons: []string{"insufficient unused quota for cpu in flavor one, 1 more needed"},
  1620  					},
  1621  					Count: 1,
  1622  				}},
  1623  				Usage: cache.FlavorResourceQuantities{"one": {"cpu": 9000, "pods": 1}},
  1624  			},
  1625  		},
  1626  		"preempt try next flavor": {
  1627  			wlPods: []kueue.PodSet{
  1628  				*utiltesting.MakePodSet("main", 1).
  1629  					Request(corev1.ResourceCPU, "9").
  1630  					Obj(),
  1631  			},
  1632  			clusterQueue: cache.ClusterQueue{
  1633  				FlavorFungibility: defaultFlavorFungibility,
  1634  				ResourceGroups: []cache.ResourceGroup{{
  1635  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  1636  					Flavors: []cache.FlavorQuotas{{
  1637  						Name: "one",
  1638  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1639  							corev1.ResourcePods: {Nominal: 10},
  1640  							corev1.ResourceCPU:  {Nominal: 10000},
  1641  						},
  1642  					}, {
  1643  						Name: "two",
  1644  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1645  							corev1.ResourcePods: {Nominal: 10},
  1646  							corev1.ResourceCPU:  {Nominal: 10000},
  1647  						},
  1648  					}},
  1649  				}},
  1650  				Usage: cache.FlavorResourceQuantities{
  1651  					"one": {corev1.ResourceCPU: 2000},
  1652  				},
  1653  			},
  1654  			wantRepMode: Fit,
  1655  			wantAssignment: Assignment{
  1656  				PodSets: []PodSetAssignment{{
  1657  					Name: "main",
  1658  					Flavors: ResourceAssignment{
  1659  						corev1.ResourceCPU:  {Name: "two", Mode: Fit},
  1660  						corev1.ResourcePods: {Name: "two", Mode: Fit},
  1661  					},
  1662  					Requests: corev1.ResourceList{
  1663  						corev1.ResourceCPU:  resource.MustParse("9000m"),
  1664  						corev1.ResourcePods: resource.MustParse("1"),
  1665  					},
  1666  					Count: 1,
  1667  				}},
  1668  				Usage: cache.FlavorResourceQuantities{"two": {"cpu": 9000, "pods": 1}},
  1669  			},
  1670  		},
  1671  		"borrow try next flavor, found the first flavor": {
  1672  			wlPods: []kueue.PodSet{
  1673  				*utiltesting.MakePodSet("main", 1).
  1674  					Request(corev1.ResourceCPU, "9").
  1675  					Obj(),
  1676  			},
  1677  			clusterQueue: cache.ClusterQueue{
  1678  				Cohort: &cache.Cohort{
  1679  					Usage: cache.FlavorResourceQuantities{
  1680  						"one": {corev1.ResourceCPU: 2000},
  1681  					},
  1682  					RequestableResources: cache.FlavorResourceQuantities{
  1683  						"one": {corev1.ResourceCPU: 11000, corev1.ResourcePods: 10},
  1684  						"two": {corev1.ResourceCPU: 1000, corev1.ResourcePods: 10},
  1685  					},
  1686  				},
  1687  				FlavorFungibility: kueue.FlavorFungibility{
  1688  					WhenCanBorrow:  kueue.TryNextFlavor,
  1689  					WhenCanPreempt: kueue.TryNextFlavor,
  1690  				},
  1691  				ResourceGroups: []cache.ResourceGroup{{
  1692  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  1693  					Flavors: []cache.FlavorQuotas{{
  1694  						Name: "one",
  1695  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1696  							corev1.ResourcePods: {Nominal: 10},
  1697  							corev1.ResourceCPU:  {Nominal: 10000, BorrowingLimit: ptr.To[int64](1000)},
  1698  						},
  1699  					}, {
  1700  						Name: "two",
  1701  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1702  							corev1.ResourcePods: {Nominal: 10},
  1703  							corev1.ResourceCPU:  {Nominal: 1000},
  1704  						},
  1705  					}},
  1706  				}},
  1707  				Usage: cache.FlavorResourceQuantities{
  1708  					"one": {corev1.ResourceCPU: 2000},
  1709  				},
  1710  			},
  1711  			wantRepMode: Fit,
  1712  			wantAssignment: Assignment{
  1713  				Borrowing: true,
  1714  				PodSets: []PodSetAssignment{{
  1715  					Name: "main",
  1716  					Flavors: ResourceAssignment{
  1717  						corev1.ResourceCPU:  {Name: "one", Mode: Fit},
  1718  						corev1.ResourcePods: {Name: "one", Mode: Fit},
  1719  					},
  1720  					Requests: corev1.ResourceList{
  1721  						corev1.ResourceCPU:  resource.MustParse("9000m"),
  1722  						corev1.ResourcePods: resource.MustParse("1"),
  1723  					},
  1724  					Count: 1,
  1725  				}},
  1726  				Usage: cache.FlavorResourceQuantities{
  1727  					"one": {corev1.ResourceCPU: 9000, corev1.ResourcePods: 1},
  1728  				},
  1729  			},
  1730  		},
  1731  		"borrow try next flavor, found the second flavor": {
  1732  			wlPods: []kueue.PodSet{
  1733  				*utiltesting.MakePodSet("main", 1).
  1734  					Request(corev1.ResourceCPU, "9").
  1735  					Obj(),
  1736  			},
  1737  			clusterQueue: cache.ClusterQueue{
  1738  				Cohort: &cache.Cohort{
  1739  					Usage: cache.FlavorResourceQuantities{
  1740  						"one": {corev1.ResourceCPU: 2000},
  1741  					},
  1742  					RequestableResources: cache.FlavorResourceQuantities{
  1743  						"one": {corev1.ResourceCPU: 11000, corev1.ResourcePods: 10},
  1744  						"two": {corev1.ResourceCPU: 10000, corev1.ResourcePods: 10},
  1745  					},
  1746  				},
  1747  				FlavorFungibility: kueue.FlavorFungibility{
  1748  					WhenCanBorrow:  kueue.TryNextFlavor,
  1749  					WhenCanPreempt: kueue.TryNextFlavor,
  1750  				},
  1751  				ResourceGroups: []cache.ResourceGroup{{
  1752  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  1753  					Flavors: []cache.FlavorQuotas{{
  1754  						Name: "one",
  1755  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1756  							corev1.ResourcePods: {Nominal: 10},
  1757  							corev1.ResourceCPU:  {Nominal: 10000, BorrowingLimit: ptr.To[int64](1000)},
  1758  						},
  1759  					}, {
  1760  						Name: "two",
  1761  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1762  							corev1.ResourcePods: {Nominal: 10},
  1763  							corev1.ResourceCPU:  {Nominal: 10000},
  1764  						},
  1765  					}},
  1766  				}},
  1767  				Usage: cache.FlavorResourceQuantities{
  1768  					"one": {corev1.ResourceCPU: 2000},
  1769  				},
  1770  			},
  1771  			wantRepMode: Fit,
  1772  			wantAssignment: Assignment{
  1773  				PodSets: []PodSetAssignment{{
  1774  					Name: "main",
  1775  					Flavors: ResourceAssignment{
  1776  						corev1.ResourceCPU:  {Name: "two", Mode: Fit},
  1777  						corev1.ResourcePods: {Name: "two", Mode: Fit},
  1778  					},
  1779  					Requests: corev1.ResourceList{
  1780  						corev1.ResourceCPU:  resource.MustParse("9000m"),
  1781  						corev1.ResourcePods: resource.MustParse("1"),
  1782  					},
  1783  					Count: 1,
  1784  				}},
  1785  				Usage: cache.FlavorResourceQuantities{
  1786  					"two": {corev1.ResourceCPU: 9000, corev1.ResourcePods: 1},
  1787  				},
  1788  			},
  1789  		},
  1790  		"borrow before try next flavor": {
  1791  			wlPods: []kueue.PodSet{
  1792  				*utiltesting.MakePodSet("main", 1).
  1793  					Request(corev1.ResourceCPU, "9").
  1794  					Obj(),
  1795  			},
  1796  			clusterQueue: cache.ClusterQueue{
  1797  				Cohort: &cache.Cohort{
  1798  					Usage: cache.FlavorResourceQuantities{
  1799  						"one": {corev1.ResourceCPU: 2000},
  1800  					},
  1801  					RequestableResources: cache.FlavorResourceQuantities{
  1802  						"one": {corev1.ResourceCPU: 11000, corev1.ResourcePods: 10},
  1803  						"two": {corev1.ResourceCPU: 10000, corev1.ResourcePods: 10},
  1804  					},
  1805  				},
  1806  				FlavorFungibility: defaultFlavorFungibility,
  1807  				ResourceGroups: []cache.ResourceGroup{{
  1808  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  1809  					Flavors: []cache.FlavorQuotas{{
  1810  						Name: "one",
  1811  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1812  							corev1.ResourcePods: {Nominal: 10},
  1813  							corev1.ResourceCPU:  {Nominal: 10000, BorrowingLimit: ptr.To[int64](1000)},
  1814  						},
  1815  					}, {
  1816  						Name: "two",
  1817  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1818  							corev1.ResourcePods: {Nominal: 10},
  1819  							corev1.ResourceCPU:  {Nominal: 10000},
  1820  						},
  1821  					}},
  1822  				}},
  1823  				Usage: cache.FlavorResourceQuantities{
  1824  					"one": {corev1.ResourceCPU: 2000},
  1825  				},
  1826  			},
  1827  			wantRepMode: Fit,
  1828  			wantAssignment: Assignment{
  1829  				Borrowing: true,
  1830  				PodSets: []PodSetAssignment{{
  1831  					Name: "main",
  1832  					Flavors: ResourceAssignment{
  1833  						corev1.ResourceCPU:  {Name: "one", Mode: Fit},
  1834  						corev1.ResourcePods: {Name: "one", Mode: Fit},
  1835  					},
  1836  					Requests: corev1.ResourceList{
  1837  						corev1.ResourceCPU:  resource.MustParse("9000m"),
  1838  						corev1.ResourcePods: resource.MustParse("1"),
  1839  					},
  1840  					Count: 1,
  1841  				}},
  1842  				Usage: cache.FlavorResourceQuantities{"one": {"cpu": 9000, "pods": 1}},
  1843  			},
  1844  		},
  1845  		"when borrowing while preemption is needed for flavor one; WhenCanBorrow=Borrow": {
  1846  			wlPods: []kueue.PodSet{
  1847  				*utiltesting.MakePodSet("main", 1).
  1848  					Request(corev1.ResourceCPU, "12").
  1849  					Obj(),
  1850  			},
  1851  			clusterQueue: cache.ClusterQueue{
  1852  				Preemption: kueue.ClusterQueuePreemption{
  1853  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
  1854  					BorrowWithinCohort: &kueue.BorrowWithinCohort{
  1855  						Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
  1856  					},
  1857  				},
  1858  				Cohort: &cache.Cohort{
  1859  					Usage: cache.FlavorResourceQuantities{
  1860  						"one": {corev1.ResourceCPU: 10000},
  1861  					},
  1862  					RequestableResources: cache.FlavorResourceQuantities{
  1863  						"one": {corev1.ResourceCPU: 12000},
  1864  						"two": {corev1.ResourceCPU: 12000},
  1865  					},
  1866  				},
  1867  				FlavorFungibility: kueue.FlavorFungibility{
  1868  					WhenCanBorrow:  kueue.Borrow,
  1869  					WhenCanPreempt: kueue.Preempt,
  1870  				},
  1871  				ResourceGroups: []cache.ResourceGroup{{
  1872  					CoveredResources: sets.New(corev1.ResourceCPU),
  1873  					Flavors: []cache.FlavorQuotas{{
  1874  						Name: "one",
  1875  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1876  							corev1.ResourceCPU: {Nominal: 0, BorrowingLimit: ptr.To[int64](12000)},
  1877  						},
  1878  					}, {
  1879  						Name: "two",
  1880  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1881  							corev1.ResourceCPU: {Nominal: 12000},
  1882  						},
  1883  					}},
  1884  				}},
  1885  			},
  1886  			wantRepMode: Preempt,
  1887  			wantAssignment: Assignment{
  1888  				Borrowing: true,
  1889  				PodSets: []PodSetAssignment{{
  1890  					Name: "main",
  1891  					Flavors: ResourceAssignment{
  1892  						corev1.ResourceCPU: {Name: "one", Mode: Preempt},
  1893  					},
  1894  					Status: &Status{
  1895  						reasons: []string{"insufficient unused quota in cohort for cpu in flavor one, 10 more needed"},
  1896  					},
  1897  					Requests: corev1.ResourceList{
  1898  						corev1.ResourceCPU: resource.MustParse("12000m"),
  1899  					},
  1900  					Count: 1,
  1901  				}},
  1902  				Usage: cache.FlavorResourceQuantities{
  1903  					"one": {corev1.ResourceCPU: 12000},
  1904  				},
  1905  			},
  1906  		},
  1907  		"when borrowing while preemption is needed for flavor one, no borrowingLimit; WhenCanBorrow=Borrow": {
  1908  			wlPods: []kueue.PodSet{
  1909  				*utiltesting.MakePodSet("main", 1).
  1910  					Request(corev1.ResourceCPU, "12").
  1911  					Obj(),
  1912  			},
  1913  			clusterQueue: cache.ClusterQueue{
  1914  				Preemption: kueue.ClusterQueuePreemption{
  1915  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
  1916  					BorrowWithinCohort: &kueue.BorrowWithinCohort{
  1917  						Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
  1918  					},
  1919  				},
  1920  				Cohort: &cache.Cohort{
  1921  					Usage: cache.FlavorResourceQuantities{
  1922  						"one": {corev1.ResourceCPU: 10000},
  1923  					},
  1924  					RequestableResources: cache.FlavorResourceQuantities{
  1925  						"one": {corev1.ResourceCPU: 12000},
  1926  						"two": {corev1.ResourceCPU: 12000},
  1927  					},
  1928  				},
  1929  				FlavorFungibility: kueue.FlavorFungibility{
  1930  					WhenCanBorrow:  kueue.Borrow,
  1931  					WhenCanPreempt: kueue.Preempt,
  1932  				},
  1933  				ResourceGroups: []cache.ResourceGroup{{
  1934  					CoveredResources: sets.New(corev1.ResourceCPU),
  1935  					Flavors: []cache.FlavorQuotas{{
  1936  						Name: "one",
  1937  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1938  							corev1.ResourceCPU: {Nominal: 0},
  1939  						},
  1940  					}, {
  1941  						Name: "two",
  1942  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  1943  							corev1.ResourceCPU: {Nominal: 12000},
  1944  						},
  1945  					}},
  1946  				}},
  1947  			},
  1948  			wantRepMode: Preempt,
  1949  			wantAssignment: Assignment{
  1950  				Borrowing: true,
  1951  				PodSets: []PodSetAssignment{{
  1952  					Name: "main",
  1953  					Flavors: ResourceAssignment{
  1954  						corev1.ResourceCPU: {Name: "one", Mode: Preempt},
  1955  					},
  1956  					Status: &Status{
  1957  						reasons: []string{"insufficient unused quota in cohort for cpu in flavor one, 10 more needed"},
  1958  					},
  1959  					Requests: corev1.ResourceList{
  1960  						corev1.ResourceCPU: resource.MustParse("12000m"),
  1961  					},
  1962  					Count: 1,
  1963  				}},
  1964  				Usage: cache.FlavorResourceQuantities{
  1965  					"one": {corev1.ResourceCPU: 12000},
  1966  				},
  1967  			},
  1968  		},
  1969  		"when borrowing while preemption is needed for flavor one; WhenCanBorrow=TryNextFlavor": {
  1970  			wlPods: []kueue.PodSet{
  1971  				*utiltesting.MakePodSet("main", 1).
  1972  					Request(corev1.ResourceCPU, "12").
  1973  					Obj(),
  1974  			},
  1975  			clusterQueue: cache.ClusterQueue{
  1976  				Preemption: kueue.ClusterQueuePreemption{
  1977  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
  1978  					BorrowWithinCohort: &kueue.BorrowWithinCohort{
  1979  						Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
  1980  					},
  1981  				},
  1982  				Cohort: &cache.Cohort{
  1983  					Usage: cache.FlavorResourceQuantities{
  1984  						"one": {corev1.ResourceCPU: 10000},
  1985  					},
  1986  					RequestableResources: cache.FlavorResourceQuantities{
  1987  						"one": {corev1.ResourceCPU: 12000},
  1988  						"two": {corev1.ResourceCPU: 12000},
  1989  					},
  1990  				},
  1991  				FlavorFungibility: kueue.FlavorFungibility{
  1992  					WhenCanBorrow:  kueue.TryNextFlavor,
  1993  					WhenCanPreempt: kueue.Preempt,
  1994  				},
  1995  				ResourceGroups: []cache.ResourceGroup{{
  1996  					CoveredResources: sets.New(corev1.ResourceCPU),
  1997  					Flavors: []cache.FlavorQuotas{{
  1998  						Name: "one",
  1999  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  2000  							corev1.ResourceCPU: {Nominal: 0, BorrowingLimit: ptr.To[int64](12000)},
  2001  						},
  2002  					}, {
  2003  						Name: "two",
  2004  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  2005  							corev1.ResourceCPU: {Nominal: 12000},
  2006  						},
  2007  					}},
  2008  				}},
  2009  			},
  2010  			wantRepMode: Fit,
  2011  			wantAssignment: Assignment{
  2012  				PodSets: []PodSetAssignment{{
  2013  					Name: "main",
  2014  					Flavors: ResourceAssignment{
  2015  						corev1.ResourceCPU: {Name: "two", Mode: Fit},
  2016  					},
  2017  					Requests: corev1.ResourceList{
  2018  						corev1.ResourceCPU: resource.MustParse("12000m"),
  2019  					},
  2020  					Count: 1,
  2021  				}},
  2022  				Usage: cache.FlavorResourceQuantities{
  2023  					"two": {corev1.ResourceCPU: 12000},
  2024  				},
  2025  			},
  2026  		},
  2027  		"when borrowing while preemption is needed, but borrowingLimit exceeds the quota available in the cohort": {
  2028  			wlPods: []kueue.PodSet{
  2029  				*utiltesting.MakePodSet("main", 1).
  2030  					Request(corev1.ResourceCPU, "12").
  2031  					Obj(),
  2032  			},
  2033  			clusterQueue: cache.ClusterQueue{
  2034  				Preemption: kueue.ClusterQueuePreemption{
  2035  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
  2036  					BorrowWithinCohort: &kueue.BorrowWithinCohort{
  2037  						Policy: kueue.BorrowWithinCohortPolicyLowerPriority,
  2038  					},
  2039  				},
  2040  				Cohort: &cache.Cohort{
  2041  					Usage: cache.FlavorResourceQuantities{
  2042  						"one": {corev1.ResourceCPU: 10000},
  2043  					},
  2044  					RequestableResources: cache.FlavorResourceQuantities{
  2045  						// below the borrowingLimit required to admit
  2046  						"one": {corev1.ResourceCPU: 11000},
  2047  					},
  2048  				},
  2049  				ResourceGroups: []cache.ResourceGroup{{
  2050  					CoveredResources: sets.New(corev1.ResourceCPU),
  2051  					Flavors: []cache.FlavorQuotas{{
  2052  						Name: "one",
  2053  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  2054  							corev1.ResourceCPU: {Nominal: 0, BorrowingLimit: ptr.To[int64](12000)},
  2055  						},
  2056  					}},
  2057  				}},
  2058  			},
  2059  			wantRepMode: NoFit,
  2060  			wantAssignment: Assignment{
  2061  				Usage: cache.FlavorResourceQuantities{},
  2062  				PodSets: []PodSetAssignment{
  2063  					{
  2064  						Name: "main",
  2065  						Status: &Status{
  2066  							reasons: []string{"insufficient unused quota in cohort for cpu in flavor one, 11 more needed"},
  2067  						},
  2068  						Requests: corev1.ResourceList{
  2069  							corev1.ResourceCPU: resource.MustParse("12000m"),
  2070  						},
  2071  						Count: 1,
  2072  					},
  2073  				},
  2074  			},
  2075  		},
  2076  		"lend try next flavor, found the second flavor": {
  2077  			wlPods: []kueue.PodSet{
  2078  				*utiltesting.MakePodSet("main", 1).
  2079  					Request(corev1.ResourceCPU, "9").
  2080  					Obj(),
  2081  			},
  2082  			clusterQueue: cache.ClusterQueue{
  2083  				Cohort: &cache.Cohort{
  2084  					Usage: cache.FlavorResourceQuantities{
  2085  						"one": {corev1.ResourceCPU: 2000},
  2086  					},
  2087  					RequestableResources: cache.FlavorResourceQuantities{
  2088  						"one": {corev1.ResourceCPU: 11000, corev1.ResourcePods: 10},
  2089  						"two": {corev1.ResourceCPU: 10000, corev1.ResourcePods: 10},
  2090  					},
  2091  				},
  2092  				FlavorFungibility: kueue.FlavorFungibility{
  2093  					WhenCanBorrow:  kueue.TryNextFlavor,
  2094  					WhenCanPreempt: kueue.TryNextFlavor,
  2095  				},
  2096  				ResourceGroups: []cache.ResourceGroup{{
  2097  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  2098  					Flavors: []cache.FlavorQuotas{{
  2099  						Name: "one",
  2100  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  2101  							corev1.ResourcePods: {Nominal: 10},
  2102  							corev1.ResourceCPU:  {Nominal: 10_000, LendingLimit: ptr.To[int64](1000)},
  2103  						},
  2104  					}, {
  2105  						Name: "two",
  2106  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  2107  							corev1.ResourcePods: {Nominal: 10},
  2108  							corev1.ResourceCPU:  {Nominal: 10_000, LendingLimit: ptr.To[int64](0)},
  2109  						},
  2110  					}},
  2111  				}},
  2112  				Usage: cache.FlavorResourceQuantities{
  2113  					"one": {corev1.ResourceCPU: 2000},
  2114  				},
  2115  				GuaranteedQuota: cache.FlavorResourceQuantities{
  2116  					"one": {
  2117  						"cpu": 9,
  2118  					},
  2119  					"two": {
  2120  						"cpu": 10,
  2121  					},
  2122  				},
  2123  			},
  2124  			wantRepMode: Fit,
  2125  			wantAssignment: Assignment{
  2126  				PodSets: []PodSetAssignment{{
  2127  					Name: "main",
  2128  					Flavors: ResourceAssignment{
  2129  						corev1.ResourceCPU:  {Name: "two", Mode: Fit},
  2130  						corev1.ResourcePods: {Name: "two", Mode: Fit},
  2131  					},
  2132  					Requests: corev1.ResourceList{
  2133  						corev1.ResourceCPU:  resource.MustParse("9000m"),
  2134  						corev1.ResourcePods: resource.MustParse("1"),
  2135  					},
  2136  					Count: 1,
  2137  				}},
  2138  				Usage: cache.FlavorResourceQuantities{
  2139  					"two": {corev1.ResourceCPU: 9000, corev1.ResourcePods: 1},
  2140  				},
  2141  			},
  2142  			enableLendingLimit: true,
  2143  		},
  2144  		"lend try next flavor, found the first flavor": {
  2145  			wlPods: []kueue.PodSet{
  2146  				*utiltesting.MakePodSet("main", 1).
  2147  					Request(corev1.ResourceCPU, "9").
  2148  					Obj(),
  2149  			},
  2150  			clusterQueue: cache.ClusterQueue{
  2151  				Cohort: &cache.Cohort{
  2152  					Usage: cache.FlavorResourceQuantities{
  2153  						"one": {corev1.ResourceCPU: 2000},
  2154  					},
  2155  					RequestableResources: cache.FlavorResourceQuantities{
  2156  						"one": {corev1.ResourceCPU: 11000, corev1.ResourcePods: 10},
  2157  						"two": {corev1.ResourceCPU: 1000, corev1.ResourcePods: 10},
  2158  					},
  2159  				},
  2160  				FlavorFungibility: kueue.FlavorFungibility{
  2161  					WhenCanBorrow:  kueue.TryNextFlavor,
  2162  					WhenCanPreempt: kueue.TryNextFlavor,
  2163  				},
  2164  				ResourceGroups: []cache.ResourceGroup{{
  2165  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  2166  					Flavors: []cache.FlavorQuotas{{
  2167  						Name: "one",
  2168  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  2169  							corev1.ResourcePods: {Nominal: 10},
  2170  							corev1.ResourceCPU:  {Nominal: 10_000, LendingLimit: ptr.To[int64](1000)},
  2171  						},
  2172  					}, {
  2173  						Name: "two",
  2174  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  2175  							corev1.ResourcePods: {Nominal: 10},
  2176  							corev1.ResourceCPU:  {Nominal: 1_000, LendingLimit: ptr.To[int64](0)},
  2177  						},
  2178  					}},
  2179  				}},
  2180  				Usage: cache.FlavorResourceQuantities{
  2181  					"one": {corev1.ResourceCPU: 2000},
  2182  				},
  2183  				GuaranteedQuota: cache.FlavorResourceQuantities{
  2184  					"one": {
  2185  						"cpu": 9,
  2186  					},
  2187  					"two": {
  2188  						"cpu": 1,
  2189  					},
  2190  				},
  2191  			},
  2192  			wantRepMode: Fit,
  2193  			wantAssignment: Assignment{
  2194  				PodSets: []PodSetAssignment{{
  2195  					Name: "main",
  2196  					Flavors: ResourceAssignment{
  2197  						corev1.ResourceCPU:  {Name: "one", Mode: Fit},
  2198  						corev1.ResourcePods: {Name: "one", Mode: Fit},
  2199  					},
  2200  					Requests: corev1.ResourceList{
  2201  						corev1.ResourceCPU:  resource.MustParse("9000m"),
  2202  						corev1.ResourcePods: resource.MustParse("1"),
  2203  					},
  2204  					Count: 1,
  2205  				}},
  2206  				Borrowing: true,
  2207  				Usage: cache.FlavorResourceQuantities{
  2208  					"one": {corev1.ResourceCPU: 9000, corev1.ResourcePods: 1},
  2209  				},
  2210  			},
  2211  			enableLendingLimit: true,
  2212  		},
  2213  		"lendingLimit exceeded, but can preempt in cohort and ClusterQueue": {
  2214  			wlPods: []kueue.PodSet{
  2215  				*utiltesting.MakePodSet("main", 1).
  2216  					Request(corev1.ResourceCPU, "9").
  2217  					Obj(),
  2218  			},
  2219  			clusterQueue: cache.ClusterQueue{
  2220  				Cohort: &cache.Cohort{
  2221  					Usage: cache.FlavorResourceQuantities{
  2222  						"one": {corev1.ResourceCPU: 2000},
  2223  					},
  2224  					RequestableResources: cache.FlavorResourceQuantities{
  2225  						"one": {corev1.ResourceCPU: 10000, corev1.ResourcePods: 10},
  2226  					},
  2227  				},
  2228  				FlavorFungibility: defaultFlavorFungibility,
  2229  				ResourceGroups: []cache.ResourceGroup{{
  2230  					CoveredResources: sets.New(corev1.ResourceCPU, corev1.ResourcePods),
  2231  					Flavors: []cache.FlavorQuotas{{
  2232  						Name: "one",
  2233  						Resources: map[corev1.ResourceName]*cache.ResourceQuota{
  2234  							corev1.ResourcePods: {Nominal: 10},
  2235  							corev1.ResourceCPU:  {Nominal: 10_000, LendingLimit: ptr.To[int64](0)},
  2236  						},
  2237  					}},
  2238  				}},
  2239  				Usage: cache.FlavorResourceQuantities{
  2240  					"one": {corev1.ResourceCPU: 2000},
  2241  				},
  2242  			},
  2243  			wantRepMode: Preempt,
  2244  			wantAssignment: Assignment{
  2245  				PodSets: []PodSetAssignment{{
  2246  					Name: "main",
  2247  					Flavors: ResourceAssignment{
  2248  						corev1.ResourceCPU:  {Name: "one", Mode: Preempt},
  2249  						corev1.ResourcePods: {Name: "one", Mode: Fit},
  2250  					},
  2251  					Requests: corev1.ResourceList{
  2252  						corev1.ResourceCPU:  resource.MustParse("9000m"),
  2253  						corev1.ResourcePods: resource.MustParse("1"),
  2254  					},
  2255  					Status: &Status{
  2256  						reasons: []string{"insufficient unused quota in cohort for cpu in flavor one, 1 more needed"},
  2257  					},
  2258  					Count: 1,
  2259  				}},
  2260  				Usage: cache.FlavorResourceQuantities{
  2261  					"one": {corev1.ResourceCPU: 9000, corev1.ResourcePods: 1},
  2262  				},
  2263  			},
  2264  			enableLendingLimit: true,
  2265  		},
  2266  	}
  2267  	for name, tc := range cases {
  2268  		t.Run(name, func(t *testing.T) {
  2269  			defer features.SetFeatureGateDuringTest(t, features.LendingLimit, tc.enableLendingLimit)()
  2270  			log := testr.NewWithOptions(t, testr.Options{
  2271  				Verbosity: 2,
  2272  			})
  2273  			wlInfo := workload.NewInfo(&kueue.Workload{
  2274  				Spec: kueue.WorkloadSpec{
  2275  					PodSets: tc.wlPods,
  2276  				},
  2277  				Status: kueue.WorkloadStatus{
  2278  					ReclaimablePods: tc.wlReclaimablePods,
  2279  				},
  2280  			})
  2281  			if tc.clusterQueue.FlavorFungibility.WhenCanBorrow == "" {
  2282  				tc.clusterQueue.FlavorFungibility.WhenCanBorrow = kueue.Borrow
  2283  			}
  2284  			if tc.clusterQueue.FlavorFungibility.WhenCanPreempt == "" {
  2285  				tc.clusterQueue.FlavorFungibility.WhenCanPreempt = kueue.TryNextFlavor
  2286  			}
  2287  			tc.clusterQueue.UpdateWithFlavors(resourceFlavors)
  2288  			tc.clusterQueue.UpdateRGByResource()
  2289  			assignment := AssignFlavors(log, wlInfo, resourceFlavors, &tc.clusterQueue, nil)
  2290  			if repMode := assignment.RepresentativeMode(); repMode != tc.wantRepMode {
  2291  				t.Errorf("e.assignFlavors(_).RepresentativeMode()=%s, want %s", repMode, tc.wantRepMode)
  2292  			}
  2293  
  2294  			if diff := cmp.Diff(tc.wantAssignment, assignment, cmpopts.IgnoreUnexported(Assignment{}, FlavorAssignment{}), cmpopts.IgnoreFields(Assignment{}, "LastState"), cmpopts.IgnoreFields(FlavorAssignment{}, "TriedFlavorIdx")); diff != "" {
  2295  				t.Errorf("Unexpected assignment (-want,+got):\n%s", diff)
  2296  			}
  2297  		})
  2298  	}
  2299  }
  2300  
  2301  func TestLastAssignmentOutdated(t *testing.T) {
  2302  	type args struct {
  2303  		wl *workload.Info
  2304  		cq *cache.ClusterQueue
  2305  	}
  2306  	tests := []struct {
  2307  		name string
  2308  		args args
  2309  		want bool
  2310  	}{
  2311  		{
  2312  			name: "Cluster queue allocatableResourceIncreasedGen increased",
  2313  			args: args{
  2314  				wl: &workload.Info{
  2315  					LastAssignment: &workload.AssigmentClusterQueueState{
  2316  						ClusterQueueGeneration: 0,
  2317  					},
  2318  				},
  2319  				cq: &cache.ClusterQueue{
  2320  					Cohort:                        nil,
  2321  					AllocatableResourceGeneration: 1,
  2322  				},
  2323  			},
  2324  			want: true,
  2325  		},
  2326  		{
  2327  			name: "Cohort allocatableResourceIncreasedGen increased",
  2328  			args: args{
  2329  				wl: &workload.Info{
  2330  					LastAssignment: &workload.AssigmentClusterQueueState{
  2331  						ClusterQueueGeneration: 0,
  2332  						CohortGeneration:       0,
  2333  					},
  2334  				},
  2335  				cq: &cache.ClusterQueue{
  2336  					Cohort: &cache.Cohort{
  2337  						AllocatableResourceGeneration: 1,
  2338  					},
  2339  					AllocatableResourceGeneration: 0,
  2340  				},
  2341  			},
  2342  			want: true,
  2343  		},
  2344  		{
  2345  			name: "AllocatableResourceGeneration not increased",
  2346  			args: args{
  2347  				wl: &workload.Info{
  2348  					LastAssignment: &workload.AssigmentClusterQueueState{
  2349  						ClusterQueueGeneration: 0,
  2350  						CohortGeneration:       0,
  2351  					},
  2352  				},
  2353  				cq: &cache.ClusterQueue{
  2354  					Cohort: &cache.Cohort{
  2355  						AllocatableResourceGeneration: 0,
  2356  					},
  2357  					AllocatableResourceGeneration: 0,
  2358  				},
  2359  			},
  2360  			want: false,
  2361  		},
  2362  	}
  2363  	for _, tt := range tests {
  2364  		t.Run(tt.name, func(t *testing.T) {
  2365  			if got := lastAssignmentOutdated(tt.args.wl, tt.args.cq); got != tt.want {
  2366  				t.Errorf("LastAssignmentOutdated() = %v, want %v", got, tt.want)
  2367  			}
  2368  		})
  2369  	}
  2370  }