sigs.k8s.io/kueue@v0.6.2/pkg/cache/cache_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 cache
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/google/go-cmp/cmp/cmpopts"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apimeta "k8s.io/apimachinery/pkg/api/meta"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/labels"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	"k8s.io/utils/ptr"
    33  	ctrl "sigs.k8s.io/controller-runtime"
    34  	"sigs.k8s.io/controller-runtime/pkg/client"
    35  
    36  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    37  	"sigs.k8s.io/kueue/pkg/features"
    38  	utiltesting "sigs.k8s.io/kueue/pkg/util/testing"
    39  	"sigs.k8s.io/kueue/pkg/workload"
    40  )
    41  
    42  func TestCacheClusterQueueOperations(t *testing.T) {
    43  	initialClusterQueues := []kueue.ClusterQueue{
    44  		*utiltesting.MakeClusterQueue("a").
    45  			ResourceGroup(
    46  				*utiltesting.MakeFlavorQuotas("default").
    47  					Resource(corev1.ResourceCPU, "10", "10").Obj()).
    48  			Cohort("one").
    49  			NamespaceSelector(nil).
    50  			Obj(),
    51  		*utiltesting.MakeClusterQueue("b").
    52  			ResourceGroup(
    53  				*utiltesting.MakeFlavorQuotas("default").
    54  					Resource(corev1.ResourceCPU, "15").Obj()).
    55  			Cohort("one").
    56  			NamespaceSelector(nil).
    57  			Obj(),
    58  		*utiltesting.MakeClusterQueue("c").
    59  			Cohort("two").
    60  			NamespaceSelector(nil).
    61  			Obj(),
    62  		*utiltesting.MakeClusterQueue("d").
    63  			NamespaceSelector(nil).
    64  			Obj(),
    65  		*utiltesting.MakeClusterQueue("e").
    66  			ResourceGroup(
    67  				*utiltesting.MakeFlavorQuotas("nonexistent-flavor").
    68  					Resource(corev1.ResourceCPU, "15").Obj()).
    69  			Cohort("two").
    70  			NamespaceSelector(nil).
    71  			Obj(),
    72  		*utiltesting.MakeClusterQueue("f").
    73  			Cohort("two").
    74  			NamespaceSelector(nil).
    75  			FlavorFungibility(kueue.FlavorFungibility{
    76  				WhenCanBorrow: kueue.TryNextFlavor,
    77  			}).
    78  			Obj(),
    79  	}
    80  	setup := func(cache *Cache) error {
    81  		cache.AddOrUpdateResourceFlavor(
    82  			utiltesting.MakeResourceFlavor("default").
    83  				Label("cpuType", "default").
    84  				Obj())
    85  		for _, c := range initialClusterQueues {
    86  			if err := cache.AddClusterQueue(context.Background(), &c); err != nil {
    87  				return fmt.Errorf("Failed adding ClusterQueue: %w", err)
    88  			}
    89  		}
    90  		return nil
    91  	}
    92  	cases := []struct {
    93  		name               string
    94  		operation          func(*Cache) error
    95  		clientObbjects     []client.Object
    96  		wantClusterQueues  map[string]*ClusterQueue
    97  		wantCohorts        map[string]sets.Set[string]
    98  		enableLendingLimit bool
    99  	}{
   100  		{
   101  			name: "add",
   102  			operation: func(cache *Cache) error {
   103  				return setup(cache)
   104  			},
   105  			wantClusterQueues: map[string]*ClusterQueue{
   106  				"a": {
   107  					Name:                          "a",
   108  					AllocatableResourceGeneration: 1,
   109  					ResourceGroups: []ResourceGroup{{
   110  						CoveredResources: sets.New(corev1.ResourceCPU),
   111  						Flavors: []FlavorQuotas{{
   112  							Name: "default",
   113  							Resources: map[corev1.ResourceName]*ResourceQuota{
   114  								corev1.ResourceCPU: {
   115  									Nominal:        10_000,
   116  									BorrowingLimit: ptr.To[int64](10_000),
   117  								},
   118  							},
   119  						}},
   120  						LabelKeys: sets.New("cpuType"),
   121  					}},
   122  					NamespaceSelector: labels.Nothing(),
   123  					FlavorFungibility: defaultFlavorFungibility,
   124  					Usage: FlavorResourceQuantities{
   125  						"default": {corev1.ResourceCPU: 0},
   126  					},
   127  					AdmittedUsage: FlavorResourceQuantities{
   128  						"default": {corev1.ResourceCPU: 0},
   129  					},
   130  					Status:     active,
   131  					Preemption: defaultPreemption,
   132  				},
   133  				"b": {
   134  					Name:                          "b",
   135  					AllocatableResourceGeneration: 1,
   136  					ResourceGroups: []ResourceGroup{{
   137  						CoveredResources: sets.New(corev1.ResourceCPU),
   138  						Flavors: []FlavorQuotas{{
   139  							Name: "default",
   140  							Resources: map[corev1.ResourceName]*ResourceQuota{
   141  								corev1.ResourceCPU: {
   142  									Nominal: 15_000,
   143  								},
   144  							},
   145  						}},
   146  						LabelKeys: sets.New("cpuType"),
   147  					}},
   148  					NamespaceSelector: labels.Nothing(),
   149  					FlavorFungibility: defaultFlavorFungibility,
   150  					Usage: FlavorResourceQuantities{
   151  						"default": {corev1.ResourceCPU: 0},
   152  					},
   153  					AdmittedUsage: FlavorResourceQuantities{
   154  						"default": {corev1.ResourceCPU: 0},
   155  					},
   156  					Status:     active,
   157  					Preemption: defaultPreemption,
   158  				},
   159  				"c": {
   160  					Name:                          "c",
   161  					AllocatableResourceGeneration: 1,
   162  					ResourceGroups:                []ResourceGroup{},
   163  					NamespaceSelector:             labels.Nothing(),
   164  					FlavorFungibility:             defaultFlavorFungibility,
   165  					Usage:                         FlavorResourceQuantities{},
   166  					Status:                        active,
   167  					Preemption:                    defaultPreemption,
   168  				},
   169  				"d": {
   170  					Name:                          "d",
   171  					AllocatableResourceGeneration: 1,
   172  					ResourceGroups:                []ResourceGroup{},
   173  					NamespaceSelector:             labels.Nothing(),
   174  					FlavorFungibility:             defaultFlavorFungibility,
   175  					Usage:                         FlavorResourceQuantities{},
   176  					Status:                        active,
   177  					Preemption:                    defaultPreemption,
   178  				},
   179  				"e": {
   180  					Name:                          "e",
   181  					AllocatableResourceGeneration: 1,
   182  					ResourceGroups: []ResourceGroup{{
   183  						CoveredResources: sets.New(corev1.ResourceCPU),
   184  						Flavors: []FlavorQuotas{{
   185  							Name: "nonexistent-flavor",
   186  							Resources: map[corev1.ResourceName]*ResourceQuota{
   187  								corev1.ResourceCPU: {
   188  									Nominal: 15_000,
   189  								},
   190  							},
   191  						}},
   192  					}},
   193  					NamespaceSelector: labels.Nothing(),
   194  					FlavorFungibility: defaultFlavorFungibility,
   195  					Usage: FlavorResourceQuantities{
   196  						"nonexistent-flavor": {corev1.ResourceCPU: 0},
   197  					},
   198  					AdmittedUsage: FlavorResourceQuantities{
   199  						"nonexistent-flavor": {corev1.ResourceCPU: 0},
   200  					},
   201  					Status:     pending,
   202  					Preemption: defaultPreemption,
   203  				},
   204  				"f": {
   205  					Name:                          "f",
   206  					AllocatableResourceGeneration: 1,
   207  					ResourceGroups:                []ResourceGroup{},
   208  					NamespaceSelector:             labels.Nothing(),
   209  					Usage:                         FlavorResourceQuantities{},
   210  					Status:                        active,
   211  					Preemption:                    defaultPreemption,
   212  					FlavorFungibility: kueue.FlavorFungibility{
   213  						WhenCanBorrow:  kueue.TryNextFlavor,
   214  						WhenCanPreempt: kueue.TryNextFlavor,
   215  					},
   216  				},
   217  			},
   218  			wantCohorts: map[string]sets.Set[string]{
   219  				"one": sets.New("a", "b"),
   220  				"two": sets.New("c", "e", "f"),
   221  			},
   222  		},
   223  		{
   224  			name: "add ClusterQueue with preemption policies",
   225  			operation: func(cache *Cache) error {
   226  				cq := utiltesting.MakeClusterQueue("foo").Preemption(kueue.ClusterQueuePreemption{
   227  					ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   228  					WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   229  				}).Obj()
   230  				if err := cache.AddClusterQueue(context.Background(), cq); err != nil {
   231  					return fmt.Errorf("Failed to add ClusterQueue: %w", err)
   232  				}
   233  				return nil
   234  			},
   235  			wantClusterQueues: map[string]*ClusterQueue{
   236  				"foo": {
   237  					Name:                          "foo",
   238  					AllocatableResourceGeneration: 1,
   239  					NamespaceSelector:             labels.Everything(),
   240  					Status:                        active,
   241  					FlavorFungibility:             defaultFlavorFungibility,
   242  					Preemption: kueue.ClusterQueuePreemption{
   243  						ReclaimWithinCohort: kueue.PreemptionPolicyLowerPriority,
   244  						WithinClusterQueue:  kueue.PreemptionPolicyLowerPriority,
   245  					},
   246  				},
   247  			},
   248  		},
   249  		{
   250  			name: "add flavors after queue capacities",
   251  			operation: func(cache *Cache) error {
   252  				for _, c := range initialClusterQueues {
   253  					if err := cache.AddClusterQueue(context.Background(), &c); err != nil {
   254  						return fmt.Errorf("Failed adding ClusterQueue: %w", err)
   255  					}
   256  				}
   257  				cache.AddOrUpdateResourceFlavor(
   258  					utiltesting.MakeResourceFlavor("default").
   259  						Label("cpuType", "default").
   260  						Obj())
   261  				return nil
   262  			},
   263  			wantClusterQueues: map[string]*ClusterQueue{
   264  				"a": {
   265  					Name:                          "a",
   266  					AllocatableResourceGeneration: 1,
   267  					ResourceGroups: []ResourceGroup{{
   268  						CoveredResources: sets.New(corev1.ResourceCPU),
   269  						Flavors: []FlavorQuotas{{
   270  							Name: "default",
   271  							Resources: map[corev1.ResourceName]*ResourceQuota{
   272  								corev1.ResourceCPU: {
   273  									Nominal:        10_000,
   274  									BorrowingLimit: ptr.To[int64](10_000),
   275  								},
   276  							},
   277  						}},
   278  						LabelKeys: sets.New("cpuType"),
   279  					}},
   280  					FlavorFungibility: defaultFlavorFungibility,
   281  					NamespaceSelector: labels.Nothing(),
   282  					Usage: FlavorResourceQuantities{
   283  						"default": {corev1.ResourceCPU: 0},
   284  					},
   285  					AdmittedUsage: FlavorResourceQuantities{
   286  						"default": {corev1.ResourceCPU: 0},
   287  					},
   288  					Status:     active,
   289  					Preemption: defaultPreemption,
   290  				},
   291  				"b": {
   292  					Name:                          "b",
   293  					AllocatableResourceGeneration: 1,
   294  					ResourceGroups: []ResourceGroup{{
   295  						CoveredResources: sets.New(corev1.ResourceCPU),
   296  						Flavors: []FlavorQuotas{{
   297  							Name: "default",
   298  							Resources: map[corev1.ResourceName]*ResourceQuota{
   299  								corev1.ResourceCPU: {
   300  									Nominal: 15_000,
   301  								},
   302  							},
   303  						}},
   304  						LabelKeys: sets.New("cpuType"),
   305  					}},
   306  					FlavorFungibility: defaultFlavorFungibility,
   307  					NamespaceSelector: labels.Nothing(),
   308  					Usage: FlavorResourceQuantities{
   309  						"default": {corev1.ResourceCPU: 0},
   310  					},
   311  					AdmittedUsage: FlavorResourceQuantities{
   312  						"default": {corev1.ResourceCPU: 0},
   313  					},
   314  					Status:     active,
   315  					Preemption: defaultPreemption,
   316  				},
   317  				"c": {
   318  					Name:                          "c",
   319  					AllocatableResourceGeneration: 1,
   320  					ResourceGroups:                []ResourceGroup{},
   321  					NamespaceSelector:             labels.Nothing(),
   322  					FlavorFungibility:             defaultFlavorFungibility,
   323  					Usage:                         FlavorResourceQuantities{},
   324  					Status:                        active,
   325  					Preemption:                    defaultPreemption,
   326  				},
   327  				"d": {
   328  					Name:                          "d",
   329  					AllocatableResourceGeneration: 1,
   330  					ResourceGroups:                []ResourceGroup{},
   331  					NamespaceSelector:             labels.Nothing(),
   332  					FlavorFungibility:             defaultFlavorFungibility,
   333  					Usage:                         FlavorResourceQuantities{},
   334  					Status:                        active,
   335  					Preemption:                    defaultPreemption,
   336  				},
   337  				"e": {
   338  					Name:                          "e",
   339  					AllocatableResourceGeneration: 1,
   340  					ResourceGroups: []ResourceGroup{{
   341  						CoveredResources: sets.New(corev1.ResourceCPU),
   342  						Flavors: []FlavorQuotas{
   343  							{
   344  								Name: "nonexistent-flavor",
   345  								Resources: map[corev1.ResourceName]*ResourceQuota{
   346  									corev1.ResourceCPU: {
   347  										Nominal: 15_000,
   348  									},
   349  								},
   350  							},
   351  						},
   352  					}},
   353  					NamespaceSelector: labels.Nothing(),
   354  					FlavorFungibility: defaultFlavorFungibility,
   355  					Usage: FlavorResourceQuantities{
   356  						"nonexistent-flavor": {corev1.ResourceCPU: 0},
   357  					},
   358  					AdmittedUsage: FlavorResourceQuantities{
   359  						"nonexistent-flavor": {corev1.ResourceCPU: 0},
   360  					},
   361  					Status:     pending,
   362  					Preemption: defaultPreemption,
   363  				},
   364  				"f": {
   365  					Name:                          "f",
   366  					AllocatableResourceGeneration: 1,
   367  					ResourceGroups:                []ResourceGroup{},
   368  					NamespaceSelector:             labels.Nothing(),
   369  					Usage:                         FlavorResourceQuantities{},
   370  					Status:                        active,
   371  					Preemption:                    defaultPreemption,
   372  					FlavorFungibility: kueue.FlavorFungibility{
   373  						WhenCanBorrow:  kueue.TryNextFlavor,
   374  						WhenCanPreempt: kueue.TryNextFlavor,
   375  					},
   376  				},
   377  			},
   378  			wantCohorts: map[string]sets.Set[string]{
   379  				"one": sets.New("a", "b"),
   380  				"two": sets.New("c", "e", "f"),
   381  			},
   382  		},
   383  		{
   384  			name: "update",
   385  			operation: func(cache *Cache) error {
   386  				err := setup(cache)
   387  				if err != nil {
   388  					return err
   389  				}
   390  				clusterQueues := []kueue.ClusterQueue{
   391  					*utiltesting.MakeClusterQueue("a").
   392  						ResourceGroup(
   393  							*utiltesting.MakeFlavorQuotas("default").
   394  								Resource(corev1.ResourceCPU, "5", "5").Obj()).
   395  						Cohort("two").
   396  						NamespaceSelector(nil).
   397  						Obj(),
   398  					*utiltesting.MakeClusterQueue("b").Cohort("one").Obj(), // remove the only resource group and set a namespace selector.
   399  					*utiltesting.MakeClusterQueue("e").
   400  						ResourceGroup(
   401  							*utiltesting.MakeFlavorQuotas("default").
   402  								Resource(corev1.ResourceCPU, "5", "5", "4").
   403  								Obj()).
   404  						Cohort("two").
   405  						NamespaceSelector(nil).
   406  						Obj(),
   407  				}
   408  				for _, c := range clusterQueues {
   409  					if err := cache.UpdateClusterQueue(&c); err != nil {
   410  						return fmt.Errorf("Failed updating ClusterQueue: %w", err)
   411  					}
   412  				}
   413  				cache.AddOrUpdateResourceFlavor(
   414  					utiltesting.MakeResourceFlavor("default").
   415  						Label("cpuType", "default").
   416  						Label("region", "central").
   417  						Obj())
   418  				return nil
   419  			},
   420  			wantClusterQueues: map[string]*ClusterQueue{
   421  				"a": {
   422  					Name:                          "a",
   423  					AllocatableResourceGeneration: 2,
   424  					ResourceGroups: []ResourceGroup{{
   425  						CoveredResources: sets.New(corev1.ResourceCPU),
   426  						Flavors: []FlavorQuotas{{
   427  							Name: "default",
   428  							Resources: map[corev1.ResourceName]*ResourceQuota{
   429  								corev1.ResourceCPU: {
   430  									Nominal:        5_000,
   431  									BorrowingLimit: ptr.To[int64](5_000),
   432  								},
   433  							},
   434  						}},
   435  						LabelKeys: sets.New("cpuType", "region"),
   436  					}},
   437  					NamespaceSelector: labels.Nothing(),
   438  					FlavorFungibility: defaultFlavorFungibility,
   439  					Usage: FlavorResourceQuantities{
   440  						"default": {corev1.ResourceCPU: 0},
   441  					},
   442  					AdmittedUsage: FlavorResourceQuantities{
   443  						"default": {corev1.ResourceCPU: 0},
   444  					},
   445  					Status:     active,
   446  					Preemption: defaultPreemption,
   447  				},
   448  				"b": {
   449  					Name:                          "b",
   450  					AllocatableResourceGeneration: 2,
   451  					ResourceGroups:                []ResourceGroup{},
   452  					NamespaceSelector:             labels.Everything(),
   453  					FlavorFungibility:             defaultFlavorFungibility,
   454  					Usage:                         FlavorResourceQuantities{},
   455  					Status:                        active,
   456  					Preemption:                    defaultPreemption,
   457  				},
   458  				"c": {
   459  					Name:                          "c",
   460  					AllocatableResourceGeneration: 1,
   461  					ResourceGroups:                []ResourceGroup{},
   462  					NamespaceSelector:             labels.Nothing(),
   463  					FlavorFungibility:             defaultFlavorFungibility,
   464  					Usage:                         FlavorResourceQuantities{},
   465  					Status:                        active,
   466  					Preemption:                    defaultPreemption,
   467  				},
   468  				"d": {
   469  					Name:                          "d",
   470  					AllocatableResourceGeneration: 1,
   471  					ResourceGroups:                []ResourceGroup{},
   472  					NamespaceSelector:             labels.Nothing(),
   473  					FlavorFungibility:             defaultFlavorFungibility,
   474  					Usage:                         FlavorResourceQuantities{},
   475  					Status:                        active,
   476  					Preemption:                    defaultPreemption,
   477  				},
   478  				"e": {
   479  					Name:                          "e",
   480  					AllocatableResourceGeneration: 2,
   481  					ResourceGroups: []ResourceGroup{{
   482  						CoveredResources: sets.New(corev1.ResourceCPU),
   483  						Flavors: []FlavorQuotas{{
   484  							Name: "default",
   485  							Resources: map[corev1.ResourceName]*ResourceQuota{
   486  								corev1.ResourceCPU: {
   487  									Nominal:        5_000,
   488  									BorrowingLimit: ptr.To[int64](5_000),
   489  									LendingLimit:   ptr.To[int64](4_000),
   490  								},
   491  							}},
   492  						},
   493  						LabelKeys: sets.New("cpuType", "region"),
   494  					}},
   495  					GuaranteedQuota: FlavorResourceQuantities{
   496  						"default": {
   497  							"cpu": 1_000,
   498  						},
   499  					},
   500  					NamespaceSelector: labels.Nothing(),
   501  					FlavorFungibility: defaultFlavorFungibility,
   502  					Usage: FlavorResourceQuantities{
   503  						"default": {corev1.ResourceCPU: 0},
   504  					},
   505  					AdmittedUsage: FlavorResourceQuantities{
   506  						"default": {corev1.ResourceCPU: 0},
   507  					},
   508  					Status:     active,
   509  					Preemption: defaultPreemption,
   510  				},
   511  				"f": {
   512  					Name:                          "f",
   513  					AllocatableResourceGeneration: 1,
   514  					ResourceGroups:                []ResourceGroup{},
   515  					NamespaceSelector:             labels.Nothing(),
   516  					Usage:                         FlavorResourceQuantities{},
   517  					Status:                        active,
   518  					Preemption:                    defaultPreemption,
   519  					FlavorFungibility: kueue.FlavorFungibility{
   520  						WhenCanBorrow:  kueue.TryNextFlavor,
   521  						WhenCanPreempt: kueue.TryNextFlavor,
   522  					},
   523  				},
   524  			},
   525  			wantCohorts: map[string]sets.Set[string]{
   526  				"one": sets.New("b"),
   527  				"two": sets.New("a", "c", "e", "f"),
   528  			},
   529  			enableLendingLimit: true,
   530  		},
   531  		{
   532  			name: "delete",
   533  			operation: func(cache *Cache) error {
   534  				err := setup(cache)
   535  				if err != nil {
   536  					return err
   537  				}
   538  				clusterQueues := []kueue.ClusterQueue{
   539  					{ObjectMeta: metav1.ObjectMeta{Name: "a"}},
   540  					{ObjectMeta: metav1.ObjectMeta{Name: "d"}},
   541  				}
   542  				for _, c := range clusterQueues {
   543  					cache.DeleteClusterQueue(&c)
   544  				}
   545  				return nil
   546  			},
   547  			wantClusterQueues: map[string]*ClusterQueue{
   548  				"b": {
   549  					Name:                          "b",
   550  					AllocatableResourceGeneration: 1,
   551  					ResourceGroups: []ResourceGroup{{
   552  						CoveredResources: sets.New(corev1.ResourceCPU),
   553  						Flavors: []FlavorQuotas{{
   554  							Name: "default",
   555  							Resources: map[corev1.ResourceName]*ResourceQuota{
   556  								corev1.ResourceCPU: {Nominal: 15_000},
   557  							},
   558  						}},
   559  						LabelKeys: sets.New("cpuType"),
   560  					}},
   561  					NamespaceSelector: labels.Nothing(),
   562  					FlavorFungibility: defaultFlavorFungibility,
   563  					Usage: FlavorResourceQuantities{
   564  						"default": {corev1.ResourceCPU: 0},
   565  					},
   566  					AdmittedUsage: FlavorResourceQuantities{
   567  						"default": {corev1.ResourceCPU: 0},
   568  					},
   569  					Status:     active,
   570  					Preemption: defaultPreemption,
   571  				},
   572  				"c": {
   573  					Name:                          "c",
   574  					AllocatableResourceGeneration: 1,
   575  					ResourceGroups:                []ResourceGroup{},
   576  					NamespaceSelector:             labels.Nothing(),
   577  					FlavorFungibility:             defaultFlavorFungibility,
   578  					Usage:                         FlavorResourceQuantities{},
   579  					Status:                        active,
   580  					Preemption:                    defaultPreemption,
   581  				},
   582  				"e": {
   583  					Name:                          "e",
   584  					AllocatableResourceGeneration: 1,
   585  					ResourceGroups: []ResourceGroup{{
   586  						CoveredResources: sets.New(corev1.ResourceCPU),
   587  						Flavors: []FlavorQuotas{
   588  							{
   589  								Name: "nonexistent-flavor",
   590  								Resources: map[corev1.ResourceName]*ResourceQuota{
   591  									corev1.ResourceCPU: {
   592  										Nominal: 15_000,
   593  									},
   594  								},
   595  							},
   596  						},
   597  					}},
   598  					NamespaceSelector: labels.Nothing(),
   599  					FlavorFungibility: defaultFlavorFungibility,
   600  					Usage: FlavorResourceQuantities{
   601  						"nonexistent-flavor": {corev1.ResourceCPU: 0},
   602  					},
   603  					AdmittedUsage: FlavorResourceQuantities{
   604  						"nonexistent-flavor": {corev1.ResourceCPU: 0},
   605  					},
   606  					Status:     pending,
   607  					Preemption: defaultPreemption,
   608  				},
   609  				"f": {
   610  					Name:                          "f",
   611  					AllocatableResourceGeneration: 1,
   612  					ResourceGroups:                []ResourceGroup{},
   613  					NamespaceSelector:             labels.Nothing(),
   614  					Usage:                         FlavorResourceQuantities{},
   615  					Status:                        active,
   616  					Preemption:                    defaultPreemption,
   617  					FlavorFungibility: kueue.FlavorFungibility{
   618  						WhenCanBorrow:  kueue.TryNextFlavor,
   619  						WhenCanPreempt: kueue.TryNextFlavor,
   620  					},
   621  				},
   622  			},
   623  			wantCohorts: map[string]sets.Set[string]{
   624  				"one": sets.New("b"),
   625  				"two": sets.New("c", "e", "f"),
   626  			},
   627  		},
   628  		{
   629  			name: "add resource flavors",
   630  			operation: func(cache *Cache) error {
   631  				err := setup(cache)
   632  				if err != nil {
   633  					return err
   634  				}
   635  				cache.AddOrUpdateResourceFlavor(&kueue.ResourceFlavor{
   636  					ObjectMeta: metav1.ObjectMeta{Name: "nonexistent-flavor"},
   637  				})
   638  				return nil
   639  			},
   640  			wantClusterQueues: map[string]*ClusterQueue{
   641  				"a": {
   642  					Name:                          "a",
   643  					AllocatableResourceGeneration: 1,
   644  					ResourceGroups: []ResourceGroup{{
   645  						CoveredResources: sets.New(corev1.ResourceCPU),
   646  						Flavors: []FlavorQuotas{{
   647  							Name: "default",
   648  							Resources: map[corev1.ResourceName]*ResourceQuota{
   649  								corev1.ResourceCPU: {
   650  									Nominal:        10_000,
   651  									BorrowingLimit: ptr.To[int64](10_000),
   652  								},
   653  							},
   654  						}},
   655  						LabelKeys: sets.New("cpuType"),
   656  					}},
   657  					NamespaceSelector: labels.Nothing(),
   658  					FlavorFungibility: defaultFlavorFungibility,
   659  					Usage: FlavorResourceQuantities{
   660  						"default": {corev1.ResourceCPU: 0},
   661  					},
   662  					AdmittedUsage: FlavorResourceQuantities{
   663  						"default": {corev1.ResourceCPU: 0},
   664  					},
   665  					Status:     active,
   666  					Preemption: defaultPreemption,
   667  				},
   668  				"b": {
   669  					Name:                          "b",
   670  					AllocatableResourceGeneration: 1,
   671  					ResourceGroups: []ResourceGroup{{
   672  						CoveredResources: sets.New(corev1.ResourceCPU),
   673  						Flavors: []FlavorQuotas{{
   674  							Name: "default",
   675  							Resources: map[corev1.ResourceName]*ResourceQuota{
   676  								corev1.ResourceCPU: {
   677  									Nominal: 15_000,
   678  								},
   679  							},
   680  						}},
   681  						LabelKeys: sets.New("cpuType"),
   682  					}},
   683  					NamespaceSelector: labels.Nothing(),
   684  					FlavorFungibility: defaultFlavorFungibility,
   685  					Usage: FlavorResourceQuantities{
   686  						"default": {corev1.ResourceCPU: 0},
   687  					},
   688  					AdmittedUsage: FlavorResourceQuantities{
   689  						"default": {corev1.ResourceCPU: 0},
   690  					},
   691  					Status:     active,
   692  					Preemption: defaultPreemption,
   693  				},
   694  				"c": {
   695  					Name:                          "c",
   696  					AllocatableResourceGeneration: 1,
   697  					ResourceGroups:                []ResourceGroup{},
   698  					NamespaceSelector:             labels.Nothing(),
   699  					FlavorFungibility:             defaultFlavorFungibility,
   700  					Usage:                         FlavorResourceQuantities{},
   701  					Status:                        active,
   702  					Preemption:                    defaultPreemption,
   703  				},
   704  				"d": {
   705  					Name:                          "d",
   706  					AllocatableResourceGeneration: 1,
   707  					ResourceGroups:                []ResourceGroup{},
   708  					NamespaceSelector:             labels.Nothing(),
   709  					FlavorFungibility:             defaultFlavorFungibility,
   710  					Usage:                         FlavorResourceQuantities{},
   711  					Status:                        active,
   712  					Preemption:                    defaultPreemption,
   713  				},
   714  				"e": {
   715  					Name:                          "e",
   716  					AllocatableResourceGeneration: 1,
   717  					ResourceGroups: []ResourceGroup{{
   718  						CoveredResources: sets.New(corev1.ResourceCPU),
   719  						Flavors: []FlavorQuotas{{
   720  							Name: "nonexistent-flavor",
   721  							Resources: map[corev1.ResourceName]*ResourceQuota{
   722  								corev1.ResourceCPU: {
   723  									Nominal: 15_000,
   724  								},
   725  							},
   726  						}},
   727  					}},
   728  					NamespaceSelector: labels.Nothing(),
   729  					FlavorFungibility: defaultFlavorFungibility,
   730  					Usage:             FlavorResourceQuantities{"nonexistent-flavor": {corev1.ResourceCPU: 0}},
   731  					AdmittedUsage:     FlavorResourceQuantities{"nonexistent-flavor": {corev1.ResourceCPU: 0}},
   732  					Status:            active,
   733  					Preemption:        defaultPreemption,
   734  				},
   735  				"f": {
   736  					Name:                          "f",
   737  					AllocatableResourceGeneration: 1,
   738  					ResourceGroups:                []ResourceGroup{},
   739  					NamespaceSelector:             labels.Nothing(),
   740  					Usage:                         FlavorResourceQuantities{},
   741  					Status:                        active,
   742  					Preemption:                    defaultPreemption,
   743  					FlavorFungibility: kueue.FlavorFungibility{
   744  						WhenCanBorrow:  kueue.TryNextFlavor,
   745  						WhenCanPreempt: kueue.TryNextFlavor,
   746  					},
   747  				},
   748  			},
   749  			wantCohorts: map[string]sets.Set[string]{
   750  				"one": sets.New("a", "b"),
   751  				"two": sets.New("c", "e", "f"),
   752  			},
   753  		},
   754  		{
   755  			name: "Add ClusterQueue with multiple resource groups",
   756  			operation: func(cache *Cache) error {
   757  				err := cache.AddClusterQueue(context.Background(),
   758  					utiltesting.MakeClusterQueue("foo").
   759  						ResourceGroup(
   760  							*utiltesting.MakeFlavorQuotas("foo").
   761  								Resource("cpu").
   762  								Resource("memory").
   763  								Obj(),
   764  							*utiltesting.MakeFlavorQuotas("bar").
   765  								Resource("cpu").
   766  								Resource("memory").
   767  								Obj(),
   768  						).
   769  						ResourceGroup(
   770  							*utiltesting.MakeFlavorQuotas("theta").Resource("example.com/gpu").Obj(),
   771  							*utiltesting.MakeFlavorQuotas("gamma").Resource("example.com/gpu").Obj(),
   772  						).
   773  						Obj())
   774  				if err != nil {
   775  					return fmt.Errorf("Adding ClusterQueue: %w", err)
   776  				}
   777  				return nil
   778  			},
   779  			wantClusterQueues: map[string]*ClusterQueue{
   780  				"foo": {
   781  					Name:                          "foo",
   782  					NamespaceSelector:             labels.Everything(),
   783  					AllocatableResourceGeneration: 1,
   784  					ResourceGroups: []ResourceGroup{
   785  						{
   786  							CoveredResources: sets.New[corev1.ResourceName]("cpu", "memory"),
   787  							Flavors: []FlavorQuotas{
   788  								{
   789  									Name: "foo",
   790  									Resources: map[corev1.ResourceName]*ResourceQuota{
   791  										"cpu":    {},
   792  										"memory": {},
   793  									},
   794  								},
   795  								{
   796  									Name: "bar",
   797  									Resources: map[corev1.ResourceName]*ResourceQuota{
   798  										"cpu":    {},
   799  										"memory": {},
   800  									},
   801  								},
   802  							},
   803  						},
   804  						{
   805  							CoveredResources: sets.New[corev1.ResourceName]("example.com/gpu"),
   806  							Flavors: []FlavorQuotas{
   807  								{
   808  									Name: "theta",
   809  									Resources: map[corev1.ResourceName]*ResourceQuota{
   810  										"example.com/gpu": {},
   811  									},
   812  								},
   813  								{
   814  									Name: "gamma",
   815  									Resources: map[corev1.ResourceName]*ResourceQuota{
   816  										"example.com/gpu": {},
   817  									},
   818  								},
   819  							},
   820  						},
   821  					},
   822  					FlavorFungibility: defaultFlavorFungibility,
   823  					Usage: FlavorResourceQuantities{
   824  						"foo": {
   825  							"cpu":    0,
   826  							"memory": 0,
   827  						},
   828  						"bar": {
   829  							"cpu":    0,
   830  							"memory": 0,
   831  						},
   832  						"theta": {
   833  							"example.com/gpu": 0,
   834  						},
   835  						"gamma": {
   836  							"example.com/gpu": 0,
   837  						},
   838  					},
   839  					AdmittedUsage: FlavorResourceQuantities{
   840  						"foo": {
   841  							"cpu":    0,
   842  							"memory": 0,
   843  						},
   844  						"bar": {
   845  							"cpu":    0,
   846  							"memory": 0,
   847  						},
   848  						"theta": {
   849  							"example.com/gpu": 0,
   850  						},
   851  						"gamma": {
   852  							"example.com/gpu": 0,
   853  						},
   854  					},
   855  					Status:     pending,
   856  					Preemption: defaultPreemption,
   857  				},
   858  			},
   859  		},
   860  		{
   861  			name: "add cluster queue with missing check",
   862  			operation: func(cache *Cache) error {
   863  				err := cache.AddClusterQueue(context.Background(),
   864  					utiltesting.MakeClusterQueue("foo").
   865  						AdmissionChecks("check1", "check2").
   866  						Obj())
   867  				if err != nil {
   868  					return fmt.Errorf("Adding ClusterQueue: %w", err)
   869  				}
   870  				return nil
   871  			},
   872  			wantClusterQueues: map[string]*ClusterQueue{
   873  				"foo": {
   874  					Name:                          "foo",
   875  					NamespaceSelector:             labels.Everything(),
   876  					Status:                        pending,
   877  					Preemption:                    defaultPreemption,
   878  					AllocatableResourceGeneration: 1,
   879  					FlavorFungibility:             defaultFlavorFungibility,
   880  					AdmissionChecks:               sets.New("check1", "check2"),
   881  				},
   882  			},
   883  			wantCohorts: map[string]sets.Set[string]{},
   884  		},
   885  		{
   886  			name: "add check after queue creation",
   887  			operation: func(cache *Cache) error {
   888  				err := cache.AddClusterQueue(context.Background(),
   889  					utiltesting.MakeClusterQueue("foo").
   890  						AdmissionChecks("check1", "check2").
   891  						Obj())
   892  				if err != nil {
   893  					return fmt.Errorf("Adding ClusterQueue: %w", err)
   894  				}
   895  
   896  				cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check1").Active(metav1.ConditionTrue).Obj())
   897  				cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Active(metav1.ConditionTrue).Obj())
   898  				return nil
   899  			},
   900  			wantClusterQueues: map[string]*ClusterQueue{
   901  				"foo": {
   902  					Name:                          "foo",
   903  					NamespaceSelector:             labels.Everything(),
   904  					Status:                        active,
   905  					Preemption:                    defaultPreemption,
   906  					AllocatableResourceGeneration: 1,
   907  					FlavorFungibility:             defaultFlavorFungibility,
   908  					AdmissionChecks:               sets.New("check1", "check2"),
   909  				},
   910  			},
   911  			wantCohorts: map[string]sets.Set[string]{},
   912  		},
   913  		{
   914  			name: "remove check after queue creation",
   915  			operation: func(cache *Cache) error {
   916  				cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check1").Active(metav1.ConditionTrue).Obj())
   917  				cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Active(metav1.ConditionTrue).Obj())
   918  				err := cache.AddClusterQueue(context.Background(),
   919  					utiltesting.MakeClusterQueue("foo").
   920  						AdmissionChecks("check1", "check2").
   921  						Obj())
   922  				if err != nil {
   923  					return fmt.Errorf("Adding ClusterQueue: %w", err)
   924  				}
   925  
   926  				cache.DeleteAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Obj())
   927  				return nil
   928  			},
   929  			wantClusterQueues: map[string]*ClusterQueue{
   930  				"foo": {
   931  					Name:                          "foo",
   932  					NamespaceSelector:             labels.Everything(),
   933  					Status:                        pending,
   934  					Preemption:                    defaultPreemption,
   935  					AllocatableResourceGeneration: 1,
   936  					FlavorFungibility:             defaultFlavorFungibility,
   937  					AdmissionChecks:               sets.New("check1", "check2"),
   938  				},
   939  			},
   940  			wantCohorts: map[string]sets.Set[string]{},
   941  		},
   942  		{
   943  			name: "inactivate check after queue creation",
   944  			operation: func(cache *Cache) error {
   945  				cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check1").Active(metav1.ConditionTrue).Obj())
   946  				cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Active(metav1.ConditionTrue).Obj())
   947  				err := cache.AddClusterQueue(context.Background(),
   948  					utiltesting.MakeClusterQueue("foo").
   949  						AdmissionChecks("check1", "check2").
   950  						Obj())
   951  				if err != nil {
   952  					return fmt.Errorf("Adding ClusterQueue: %w", err)
   953  				}
   954  
   955  				cache.AddOrUpdateAdmissionCheck(utiltesting.MakeAdmissionCheck("check2").Active(metav1.ConditionFalse).Obj())
   956  				return nil
   957  			},
   958  			wantClusterQueues: map[string]*ClusterQueue{
   959  				"foo": {
   960  					Name:                          "foo",
   961  					NamespaceSelector:             labels.Everything(),
   962  					Status:                        pending,
   963  					Preemption:                    defaultPreemption,
   964  					AllocatableResourceGeneration: 1,
   965  					FlavorFungibility:             defaultFlavorFungibility,
   966  					AdmissionChecks:               sets.New("check1", "check2"),
   967  				},
   968  			},
   969  			wantCohorts: map[string]sets.Set[string]{},
   970  		},
   971  		{
   972  			name: "add cluster queue after finished workloads",
   973  			clientObbjects: []client.Object{
   974  				utiltesting.MakeLocalQueue("lq1", "ns").ClusterQueue("cq1").Obj(),
   975  				utiltesting.MakeWorkload("pending", "ns").Obj(),
   976  				utiltesting.MakeWorkload("reserving", "ns").ReserveQuota(
   977  					utiltesting.MakeAdmission("cq1").Assignment(corev1.ResourceCPU, "f1", "1").Obj(),
   978  				).Obj(),
   979  				utiltesting.MakeWorkload("admitted", "ns").ReserveQuota(
   980  					utiltesting.MakeAdmission("cq1").Assignment(corev1.ResourceCPU, "f1", "1").Obj(),
   981  				).Admitted(true).Obj(),
   982  				utiltesting.MakeWorkload("finished", "ns").ReserveQuota(
   983  					utiltesting.MakeAdmission("cq1").Assignment(corev1.ResourceCPU, "f1", "1").Obj(),
   984  				).Admitted(true).Finished().Obj(),
   985  			},
   986  			operation: func(cache *Cache) error {
   987  				cache.AddOrUpdateResourceFlavor(utiltesting.MakeResourceFlavor("f1").Obj())
   988  				err := cache.AddClusterQueue(context.Background(),
   989  					utiltesting.MakeClusterQueue("cq1").
   990  						ResourceGroup(kueue.FlavorQuotas{
   991  							Name: "f1",
   992  							Resources: []kueue.ResourceQuota{
   993  								{
   994  									Name:         corev1.ResourceCPU,
   995  									NominalQuota: resource.MustParse("10"),
   996  								},
   997  							},
   998  						}).
   999  						Obj())
  1000  				if err != nil {
  1001  					return fmt.Errorf("Adding ClusterQueue: %w", err)
  1002  				}
  1003  				return nil
  1004  			},
  1005  			wantClusterQueues: map[string]*ClusterQueue{
  1006  				"cq1": {
  1007  					Name:                          "cq1",
  1008  					NamespaceSelector:             labels.Everything(),
  1009  					Status:                        active,
  1010  					Preemption:                    defaultPreemption,
  1011  					AllocatableResourceGeneration: 1,
  1012  					FlavorFungibility:             defaultFlavorFungibility,
  1013  					Usage:                         FlavorResourceQuantities{"f1": {corev1.ResourceCPU: 2000}},
  1014  					AdmittedUsage:                 FlavorResourceQuantities{"f1": {corev1.ResourceCPU: 1000}},
  1015  					Workloads: map[string]*workload.Info{
  1016  						"ns/reserving": {
  1017  							ClusterQueue: "cq1",
  1018  							TotalRequests: []workload.PodSetResources{
  1019  								{
  1020  									Name:     "main",
  1021  									Requests: workload.Requests{corev1.ResourceCPU: 1000},
  1022  									Count:    1,
  1023  									Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
  1024  										corev1.ResourceCPU: "f1",
  1025  									},
  1026  								},
  1027  							},
  1028  						},
  1029  						"ns/admitted": {
  1030  							ClusterQueue: "cq1",
  1031  							TotalRequests: []workload.PodSetResources{
  1032  								{
  1033  									Name:     "main",
  1034  									Requests: workload.Requests{corev1.ResourceCPU: 1000},
  1035  									Count:    1,
  1036  									Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
  1037  										corev1.ResourceCPU: "f1",
  1038  									},
  1039  								},
  1040  							},
  1041  						},
  1042  					},
  1043  				},
  1044  			},
  1045  			wantCohorts: map[string]sets.Set[string]{},
  1046  		},
  1047  	}
  1048  
  1049  	for _, tc := range cases {
  1050  		t.Run(tc.name, func(t *testing.T) {
  1051  			defer features.SetFeatureGateDuringTest(t, features.LendingLimit, tc.enableLendingLimit)()
  1052  			cache := New(utiltesting.NewFakeClient(tc.clientObbjects...))
  1053  			if err := tc.operation(cache); err != nil {
  1054  				t.Errorf("Unexpected error during test operation: %s", err)
  1055  			}
  1056  			if diff := cmp.Diff(tc.wantClusterQueues, cache.clusterQueues,
  1057  				cmpopts.IgnoreFields(ClusterQueue{}, "Cohort", "RGByResource", "ResourceGroups"),
  1058  				cmpopts.IgnoreFields(workload.Info{}, "Obj", "LastAssignment"),
  1059  				cmpopts.IgnoreUnexported(ClusterQueue{}),
  1060  				cmpopts.EquateEmpty()); diff != "" {
  1061  				t.Errorf("Unexpected clusterQueues (-want,+got):\n%s", diff)
  1062  			}
  1063  			for _, cq := range cache.clusterQueues {
  1064  				for i := range cq.ResourceGroups {
  1065  					rg := &cq.ResourceGroups[i]
  1066  					for rName := range rg.CoveredResources {
  1067  						if cq.RGByResource[rName] != rg {
  1068  							t.Errorf("RGByResource[%s] does not point to its resource group", rName)
  1069  						}
  1070  					}
  1071  				}
  1072  			}
  1073  
  1074  			gotCohorts := make(map[string]sets.Set[string], len(cache.cohorts))
  1075  			for name, cohort := range cache.cohorts {
  1076  				gotCohort := sets.New[string]()
  1077  				for cq := range cohort.Members {
  1078  					gotCohort.Insert(cq.Name)
  1079  				}
  1080  				gotCohorts[name] = gotCohort
  1081  			}
  1082  			if diff := cmp.Diff(tc.wantCohorts, gotCohorts, cmpopts.EquateEmpty()); diff != "" {
  1083  				t.Errorf("Unexpected cohorts (-want,+got):\n%s", diff)
  1084  			}
  1085  		})
  1086  	}
  1087  }
  1088  
  1089  func TestCacheWorkloadOperations(t *testing.T) {
  1090  	clusterQueues := []kueue.ClusterQueue{
  1091  		*utiltesting.MakeClusterQueue("one").
  1092  			ResourceGroup(
  1093  				*utiltesting.MakeFlavorQuotas("on-demand").Resource("cpu").Obj(),
  1094  				*utiltesting.MakeFlavorQuotas("spot").Resource("cpu").Obj(),
  1095  			).
  1096  			NamespaceSelector(nil).
  1097  			Obj(),
  1098  		*utiltesting.MakeClusterQueue("two").
  1099  			ResourceGroup(
  1100  				*utiltesting.MakeFlavorQuotas("on-demand").Resource("cpu").Obj(),
  1101  				*utiltesting.MakeFlavorQuotas("spot").Resource("cpu").Obj(),
  1102  			).
  1103  			NamespaceSelector(nil).
  1104  			Obj(),
  1105  	}
  1106  	podSets := []kueue.PodSet{
  1107  		*utiltesting.MakePodSet("driver", 1).
  1108  			Request(corev1.ResourceCPU, "10m").
  1109  			Request(corev1.ResourceMemory, "512Ki").
  1110  			Obj(),
  1111  		*utiltesting.MakePodSet("workers", 3).
  1112  			Request(corev1.ResourceCPU, "5m").
  1113  			Obj(),
  1114  	}
  1115  	podSetFlavors := []kueue.PodSetAssignment{
  1116  		{
  1117  			Name: "driver",
  1118  			Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
  1119  				corev1.ResourceCPU: "on-demand",
  1120  			},
  1121  			ResourceUsage: corev1.ResourceList{
  1122  				corev1.ResourceCPU: resource.MustParse("10m"),
  1123  			},
  1124  		},
  1125  		{
  1126  			Name: "workers",
  1127  			Flavors: map[corev1.ResourceName]kueue.ResourceFlavorReference{
  1128  				corev1.ResourceCPU: "spot",
  1129  			},
  1130  			ResourceUsage: corev1.ResourceList{
  1131  				corev1.ResourceCPU: resource.MustParse("15m"),
  1132  			},
  1133  		},
  1134  	}
  1135  	cl := utiltesting.NewFakeClient(
  1136  		utiltesting.MakeWorkload("a", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1137  			ClusterQueue:      "one",
  1138  			PodSetAssignments: podSetFlavors,
  1139  		}).Obj(),
  1140  		utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{
  1141  			ClusterQueue: "one",
  1142  		}).Obj(),
  1143  		utiltesting.MakeWorkload("c", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1144  			ClusterQueue: "two",
  1145  		}).Obj())
  1146  
  1147  	type result struct {
  1148  		Workloads     sets.Set[string]
  1149  		UsedResources FlavorResourceQuantities
  1150  	}
  1151  
  1152  	steps := []struct {
  1153  		name                 string
  1154  		operation            func(cache *Cache) error
  1155  		wantResults          map[string]result
  1156  		wantAssumedWorkloads map[string]string
  1157  		wantError            string
  1158  	}{
  1159  		{
  1160  			name: "add",
  1161  			operation: func(cache *Cache) error {
  1162  				workloads := []*kueue.Workload{
  1163  					utiltesting.MakeWorkload("a", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1164  						ClusterQueue:      "one",
  1165  						PodSetAssignments: podSetFlavors,
  1166  					}).Obj(),
  1167  					utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1168  						ClusterQueue: "two",
  1169  					}).Obj(),
  1170  					utiltesting.MakeWorkload("pending", "").Obj(),
  1171  				}
  1172  				for i := range workloads {
  1173  					cache.AddOrUpdateWorkload(workloads[i])
  1174  				}
  1175  				return nil
  1176  			},
  1177  			wantResults: map[string]result{
  1178  				"one": {
  1179  					Workloads: sets.New("/a", "/b"),
  1180  					UsedResources: FlavorResourceQuantities{
  1181  						"on-demand": {corev1.ResourceCPU: 10},
  1182  						"spot":      {corev1.ResourceCPU: 15},
  1183  					},
  1184  				},
  1185  				"two": {
  1186  					Workloads: sets.New("/c", "/d"),
  1187  					UsedResources: FlavorResourceQuantities{
  1188  						"on-demand": {corev1.ResourceCPU: 0},
  1189  						"spot":      {corev1.ResourceCPU: 0},
  1190  					},
  1191  				},
  1192  			},
  1193  		},
  1194  		{
  1195  			name: "add error clusterQueue doesn't exist",
  1196  			operation: func(cache *Cache) error {
  1197  				w := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1198  					ClusterQueue: "three",
  1199  				}).Obj()
  1200  				if !cache.AddOrUpdateWorkload(w) {
  1201  					return fmt.Errorf("failed to add workload")
  1202  				}
  1203  				return nil
  1204  			},
  1205  			wantError: "failed to add workload",
  1206  			wantResults: map[string]result{
  1207  				"one": {
  1208  					Workloads: sets.New("/a", "/b"),
  1209  					UsedResources: FlavorResourceQuantities{
  1210  						"on-demand": {corev1.ResourceCPU: 10},
  1211  						"spot":      {corev1.ResourceCPU: 15},
  1212  					},
  1213  				},
  1214  				"two": {
  1215  					Workloads: sets.New("/c"),
  1216  					UsedResources: FlavorResourceQuantities{
  1217  						"on-demand": {corev1.ResourceCPU: 0},
  1218  						"spot":      {corev1.ResourceCPU: 0},
  1219  					},
  1220  				},
  1221  			},
  1222  		},
  1223  		{
  1224  			name: "add already exists",
  1225  			operation: func(cache *Cache) error {
  1226  				w := utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{
  1227  					ClusterQueue: "one",
  1228  				}).Obj()
  1229  				if !cache.AddOrUpdateWorkload(w) {
  1230  					return fmt.Errorf("failed to add workload")
  1231  				}
  1232  				return nil
  1233  			},
  1234  			wantResults: map[string]result{
  1235  				"one": {
  1236  					Workloads: sets.New("/a", "/b"),
  1237  					UsedResources: FlavorResourceQuantities{
  1238  						"on-demand": {corev1.ResourceCPU: 10},
  1239  						"spot":      {corev1.ResourceCPU: 15},
  1240  					},
  1241  				},
  1242  				"two": {
  1243  					Workloads: sets.New("/c"),
  1244  					UsedResources: FlavorResourceQuantities{
  1245  						"on-demand": {corev1.ResourceCPU: 0},
  1246  						"spot":      {corev1.ResourceCPU: 0},
  1247  					},
  1248  				},
  1249  			},
  1250  		},
  1251  		{
  1252  			name: "update",
  1253  			operation: func(cache *Cache) error {
  1254  				old := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  1255  					ClusterQueue: "one",
  1256  				}).Obj()
  1257  				latest := utiltesting.MakeWorkload("a", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1258  					ClusterQueue:      "two",
  1259  					PodSetAssignments: podSetFlavors,
  1260  				}).Obj()
  1261  				return cache.UpdateWorkload(old, latest)
  1262  			},
  1263  			wantResults: map[string]result{
  1264  				"one": {
  1265  					Workloads: sets.New("/b"),
  1266  					UsedResources: FlavorResourceQuantities{
  1267  						"on-demand": {corev1.ResourceCPU: 0},
  1268  						"spot":      {corev1.ResourceCPU: 0},
  1269  					},
  1270  				},
  1271  				"two": {
  1272  					Workloads: sets.New("/a", "/c"),
  1273  					UsedResources: FlavorResourceQuantities{
  1274  						"on-demand": {corev1.ResourceCPU: 10},
  1275  						"spot":      {corev1.ResourceCPU: 15},
  1276  					},
  1277  				},
  1278  			},
  1279  		},
  1280  		{
  1281  			name: "update error old clusterQueue doesn't exist",
  1282  			operation: func(cache *Cache) error {
  1283  				old := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1284  					ClusterQueue: "three",
  1285  				}).Obj()
  1286  				latest := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1287  					ClusterQueue: "one",
  1288  				}).Obj()
  1289  				return cache.UpdateWorkload(old, latest)
  1290  			},
  1291  			wantError: "old ClusterQueue doesn't exist",
  1292  			wantResults: map[string]result{
  1293  				"one": {
  1294  					Workloads: sets.New("/a", "/b"),
  1295  					UsedResources: FlavorResourceQuantities{
  1296  						"on-demand": {corev1.ResourceCPU: 10},
  1297  						"spot":      {corev1.ResourceCPU: 15},
  1298  					},
  1299  				},
  1300  				"two": {
  1301  					Workloads: sets.New("/c"),
  1302  					UsedResources: FlavorResourceQuantities{
  1303  						"on-demand": {corev1.ResourceCPU: 0},
  1304  						"spot":      {corev1.ResourceCPU: 0},
  1305  					},
  1306  				},
  1307  			},
  1308  		},
  1309  		{
  1310  			name: "update error new clusterQueue doesn't exist",
  1311  			operation: func(cache *Cache) error {
  1312  				old := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1313  					ClusterQueue: "one",
  1314  				}).Obj()
  1315  				latest := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1316  					ClusterQueue: "three",
  1317  				}).Obj()
  1318  				return cache.UpdateWorkload(old, latest)
  1319  			},
  1320  			wantError: "new ClusterQueue doesn't exist",
  1321  			wantResults: map[string]result{
  1322  				"one": {
  1323  					Workloads: sets.New("/a", "/b"),
  1324  					UsedResources: FlavorResourceQuantities{
  1325  						"on-demand": {corev1.ResourceCPU: 10},
  1326  						"spot":      {corev1.ResourceCPU: 15},
  1327  					},
  1328  				},
  1329  				"two": {
  1330  					Workloads: sets.New("/c"),
  1331  					UsedResources: FlavorResourceQuantities{
  1332  						"on-demand": {corev1.ResourceCPU: 0},
  1333  						"spot":      {corev1.ResourceCPU: 0},
  1334  					},
  1335  				},
  1336  			},
  1337  		},
  1338  		{
  1339  			name: "update workload which doesn't exist.",
  1340  			operation: func(cache *Cache) error {
  1341  				old := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1342  					ClusterQueue: "one",
  1343  				}).Obj()
  1344  				latest := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1345  					ClusterQueue: "two",
  1346  				}).Obj()
  1347  				return cache.UpdateWorkload(old, latest)
  1348  			},
  1349  			wantResults: map[string]result{
  1350  				"one": {
  1351  					Workloads: sets.New("/a", "/b"),
  1352  					UsedResources: FlavorResourceQuantities{
  1353  						"on-demand": {corev1.ResourceCPU: 10},
  1354  						"spot":      {corev1.ResourceCPU: 15},
  1355  					},
  1356  				},
  1357  				"two": {
  1358  					Workloads: sets.New("/c", "/d"),
  1359  					UsedResources: FlavorResourceQuantities{
  1360  						"on-demand": {corev1.ResourceCPU: 0},
  1361  						"spot":      {corev1.ResourceCPU: 0},
  1362  					},
  1363  				},
  1364  			},
  1365  		},
  1366  		{
  1367  			name: "delete",
  1368  			operation: func(cache *Cache) error {
  1369  				w := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  1370  					ClusterQueue: "one",
  1371  				}).Obj()
  1372  				return cache.DeleteWorkload(w)
  1373  			},
  1374  			wantResults: map[string]result{
  1375  				"one": {
  1376  					Workloads: sets.New("/b"),
  1377  					UsedResources: FlavorResourceQuantities{
  1378  						"on-demand": {corev1.ResourceCPU: 0},
  1379  						"spot":      {corev1.ResourceCPU: 0},
  1380  					},
  1381  				},
  1382  				"two": {
  1383  					Workloads: sets.New("/c"),
  1384  					UsedResources: FlavorResourceQuantities{
  1385  						"on-demand": {corev1.ResourceCPU: 0},
  1386  						"spot":      {corev1.ResourceCPU: 0},
  1387  					},
  1388  				},
  1389  			},
  1390  		},
  1391  		{
  1392  			name: "delete workload with cancelled admission",
  1393  			operation: func(cache *Cache) error {
  1394  				w := utiltesting.MakeWorkload("a", "").Obj()
  1395  				return cache.DeleteWorkload(w)
  1396  			},
  1397  			wantResults: map[string]result{
  1398  				"one": {
  1399  					Workloads: sets.New("/b"),
  1400  					UsedResources: FlavorResourceQuantities{
  1401  						"on-demand": {corev1.ResourceCPU: 0},
  1402  						"spot":      {corev1.ResourceCPU: 0},
  1403  					},
  1404  				},
  1405  				"two": {
  1406  					Workloads: sets.New("/c"),
  1407  					UsedResources: FlavorResourceQuantities{
  1408  						"on-demand": {corev1.ResourceCPU: 0},
  1409  						"spot":      {corev1.ResourceCPU: 0},
  1410  					},
  1411  				},
  1412  			},
  1413  		},
  1414  		{
  1415  			name: "attempt deleting non-existing workload with cancelled admission",
  1416  			operation: func(cache *Cache) error {
  1417  				w := utiltesting.MakeWorkload("d", "").Obj()
  1418  				return cache.DeleteWorkload(w)
  1419  			},
  1420  			wantError: "cluster queue not found",
  1421  			wantResults: map[string]result{
  1422  				"one": {
  1423  					Workloads: sets.New("/a", "/b"),
  1424  					UsedResources: FlavorResourceQuantities{
  1425  						"on-demand": {corev1.ResourceCPU: 10},
  1426  						"spot":      {corev1.ResourceCPU: 15},
  1427  					},
  1428  				},
  1429  				"two": {
  1430  					Workloads: sets.New("/c"),
  1431  					UsedResources: FlavorResourceQuantities{
  1432  						"on-demand": {corev1.ResourceCPU: 0},
  1433  						"spot":      {corev1.ResourceCPU: 0},
  1434  					},
  1435  				},
  1436  			},
  1437  		},
  1438  		{
  1439  			name: "delete error clusterQueue doesn't exist",
  1440  			operation: func(cache *Cache) error {
  1441  				w := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  1442  					ClusterQueue: "three",
  1443  				}).Obj()
  1444  				return cache.DeleteWorkload(w)
  1445  			},
  1446  			wantError: "cluster queue not found",
  1447  			wantResults: map[string]result{
  1448  				"one": {
  1449  					Workloads: sets.New("/a", "/b"),
  1450  					UsedResources: FlavorResourceQuantities{
  1451  						"on-demand": {corev1.ResourceCPU: 10},
  1452  						"spot":      {corev1.ResourceCPU: 15},
  1453  					},
  1454  				},
  1455  				"two": {
  1456  					Workloads: sets.New("/c"),
  1457  					UsedResources: FlavorResourceQuantities{
  1458  						"on-demand": {corev1.ResourceCPU: 0},
  1459  						"spot":      {corev1.ResourceCPU: 0},
  1460  					},
  1461  				},
  1462  			},
  1463  		},
  1464  		{
  1465  			name: "delete workload which doesn't exist",
  1466  			operation: func(cache *Cache) error {
  1467  				w := utiltesting.MakeWorkload("d", "").ReserveQuota(&kueue.Admission{
  1468  					ClusterQueue: "one",
  1469  				}).Obj()
  1470  				return cache.DeleteWorkload(w)
  1471  			},
  1472  			wantResults: map[string]result{
  1473  				"one": {
  1474  					Workloads: sets.New("/a", "/b"),
  1475  					UsedResources: FlavorResourceQuantities{
  1476  						"on-demand": {corev1.ResourceCPU: 10},
  1477  						"spot":      {corev1.ResourceCPU: 15},
  1478  					},
  1479  				},
  1480  				"two": {
  1481  					Workloads: sets.New("/c"),
  1482  					UsedResources: FlavorResourceQuantities{
  1483  						"on-demand": {corev1.ResourceCPU: 0},
  1484  						"spot":      {corev1.ResourceCPU: 0},
  1485  					},
  1486  				},
  1487  			},
  1488  		},
  1489  		{
  1490  			name: "assume",
  1491  			operation: func(cache *Cache) error {
  1492  				workloads := []*kueue.Workload{
  1493  					utiltesting.MakeWorkload("d", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1494  						ClusterQueue:      "one",
  1495  						PodSetAssignments: podSetFlavors,
  1496  					}).Obj(),
  1497  					utiltesting.MakeWorkload("e", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1498  						ClusterQueue:      "two",
  1499  						PodSetAssignments: podSetFlavors,
  1500  					}).Obj(),
  1501  				}
  1502  				for i := range workloads {
  1503  					if err := cache.AssumeWorkload(workloads[i]); err != nil {
  1504  						return err
  1505  					}
  1506  				}
  1507  				return nil
  1508  			},
  1509  			wantResults: map[string]result{
  1510  				"one": {
  1511  					Workloads: sets.New("/a", "/b", "/d"),
  1512  					UsedResources: FlavorResourceQuantities{
  1513  						"on-demand": {corev1.ResourceCPU: 20},
  1514  						"spot":      {corev1.ResourceCPU: 30},
  1515  					},
  1516  				},
  1517  				"two": {
  1518  					Workloads: sets.New("/c", "/e"),
  1519  					UsedResources: FlavorResourceQuantities{
  1520  						"on-demand": {corev1.ResourceCPU: 10},
  1521  						"spot":      {corev1.ResourceCPU: 15},
  1522  					},
  1523  				},
  1524  			},
  1525  			wantAssumedWorkloads: map[string]string{
  1526  				"/d": "one",
  1527  				"/e": "two",
  1528  			},
  1529  		},
  1530  		{
  1531  			name: "assume error clusterQueue doesn't exist",
  1532  			operation: func(cache *Cache) error {
  1533  				w := utiltesting.MakeWorkload("d", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1534  					ClusterQueue: "three",
  1535  				}).Obj()
  1536  				if err := cache.AssumeWorkload(w); err != nil {
  1537  					return err
  1538  				}
  1539  				return nil
  1540  			},
  1541  			wantError: "cluster queue not found",
  1542  			wantResults: map[string]result{
  1543  				"one": {
  1544  					Workloads: sets.New("/a", "/b"),
  1545  					UsedResources: FlavorResourceQuantities{
  1546  						"on-demand": {corev1.ResourceCPU: 10},
  1547  						"spot":      {corev1.ResourceCPU: 15},
  1548  					},
  1549  				},
  1550  				"two": {
  1551  					Workloads: sets.New("/c"),
  1552  					UsedResources: FlavorResourceQuantities{
  1553  						"on-demand": {corev1.ResourceCPU: 0},
  1554  						"spot":      {corev1.ResourceCPU: 0},
  1555  					},
  1556  				},
  1557  			},
  1558  			wantAssumedWorkloads: map[string]string{},
  1559  		},
  1560  		{
  1561  			name: "forget",
  1562  			operation: func(cache *Cache) error {
  1563  				workloads := []*kueue.Workload{
  1564  					utiltesting.MakeWorkload("d", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1565  						ClusterQueue:      "one",
  1566  						PodSetAssignments: podSetFlavors,
  1567  					}).Obj(),
  1568  					utiltesting.MakeWorkload("e", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1569  						ClusterQueue:      "two",
  1570  						PodSetAssignments: podSetFlavors,
  1571  					}).Obj(),
  1572  				}
  1573  				for i := range workloads {
  1574  					if err := cache.AssumeWorkload(workloads[i]); err != nil {
  1575  						return err
  1576  					}
  1577  				}
  1578  
  1579  				w := workloads[0]
  1580  				return cache.ForgetWorkload(w)
  1581  			},
  1582  			wantResults: map[string]result{
  1583  				"one": {
  1584  					Workloads: sets.New("/a", "/b"),
  1585  					UsedResources: FlavorResourceQuantities{
  1586  						"on-demand": {corev1.ResourceCPU: 10},
  1587  						"spot":      {corev1.ResourceCPU: 15},
  1588  					},
  1589  				},
  1590  				"two": {
  1591  					Workloads: sets.New("/c", "/e"),
  1592  					UsedResources: FlavorResourceQuantities{
  1593  						"on-demand": {corev1.ResourceCPU: 10},
  1594  						"spot":      {corev1.ResourceCPU: 15},
  1595  					},
  1596  				},
  1597  			},
  1598  			wantAssumedWorkloads: map[string]string{
  1599  				"/e": "two",
  1600  			},
  1601  		},
  1602  		{
  1603  			name: "forget error workload is not assumed",
  1604  			operation: func(cache *Cache) error {
  1605  				w := utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{
  1606  					ClusterQueue: "one",
  1607  				}).Obj()
  1608  				if err := cache.ForgetWorkload(w); err != nil {
  1609  					return err
  1610  				}
  1611  				return nil
  1612  			},
  1613  			wantError: "the workload is not assumed",
  1614  			wantResults: map[string]result{
  1615  				"one": {
  1616  					Workloads: sets.New("/a", "/b"),
  1617  					UsedResources: FlavorResourceQuantities{
  1618  						"on-demand": {corev1.ResourceCPU: 10},
  1619  						"spot":      {corev1.ResourceCPU: 15},
  1620  					},
  1621  				},
  1622  				"two": {
  1623  					Workloads: sets.New("/c"),
  1624  					UsedResources: FlavorResourceQuantities{
  1625  						"on-demand": {corev1.ResourceCPU: 0},
  1626  						"spot":      {corev1.ResourceCPU: 0},
  1627  					},
  1628  				},
  1629  			},
  1630  		},
  1631  		{
  1632  			name: "add assumed workload",
  1633  			operation: func(cache *Cache) error {
  1634  				workloads := []*kueue.Workload{
  1635  					utiltesting.MakeWorkload("d", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1636  						ClusterQueue:      "one",
  1637  						PodSetAssignments: podSetFlavors,
  1638  					}).Obj(),
  1639  					utiltesting.MakeWorkload("e", "").PodSets(podSets...).ReserveQuota(&kueue.Admission{
  1640  						ClusterQueue:      "two",
  1641  						PodSetAssignments: podSetFlavors,
  1642  					}).Obj(),
  1643  				}
  1644  				for i := range workloads {
  1645  					if err := cache.AssumeWorkload(workloads[i]); err != nil {
  1646  						return err
  1647  					}
  1648  				}
  1649  
  1650  				w := workloads[0]
  1651  				if !cache.AddOrUpdateWorkload(w) {
  1652  					return fmt.Errorf("failed to add workload")
  1653  				}
  1654  				return nil
  1655  			},
  1656  			wantResults: map[string]result{
  1657  				"one": {
  1658  					Workloads: sets.New("/a", "/b", "/d"),
  1659  					UsedResources: FlavorResourceQuantities{
  1660  						"on-demand": {corev1.ResourceCPU: 20},
  1661  						"spot":      {corev1.ResourceCPU: 30},
  1662  					},
  1663  				},
  1664  				"two": {
  1665  					Workloads: sets.New("/c", "/e"),
  1666  					UsedResources: FlavorResourceQuantities{
  1667  						"on-demand": {corev1.ResourceCPU: 10},
  1668  						"spot":      {corev1.ResourceCPU: 15},
  1669  					},
  1670  				},
  1671  			},
  1672  			wantAssumedWorkloads: map[string]string{
  1673  				"/e": "two",
  1674  			},
  1675  		},
  1676  	}
  1677  	for _, step := range steps {
  1678  		t.Run(step.name, func(t *testing.T) {
  1679  			cache := New(cl)
  1680  
  1681  			for _, c := range clusterQueues {
  1682  				if err := cache.AddClusterQueue(context.Background(), &c); err != nil {
  1683  					t.Fatalf("Failed adding clusterQueue: %v", err)
  1684  				}
  1685  			}
  1686  
  1687  			gotError := step.operation(cache)
  1688  			if diff := cmp.Diff(step.wantError, messageOrEmpty(gotError)); diff != "" {
  1689  				t.Errorf("Unexpected error (-want,+got):\n%s", diff)
  1690  			}
  1691  			gotWorkloads := make(map[string]result)
  1692  			for name, cq := range cache.clusterQueues {
  1693  				gotWorkloads[name] = result{Workloads: sets.KeySet(cq.Workloads), UsedResources: cq.Usage}
  1694  			}
  1695  			if diff := cmp.Diff(step.wantResults, gotWorkloads); diff != "" {
  1696  				t.Errorf("Unexpected clusterQueues (-want,+got):\n%s", diff)
  1697  			}
  1698  			if step.wantAssumedWorkloads == nil {
  1699  				step.wantAssumedWorkloads = map[string]string{}
  1700  			}
  1701  			if diff := cmp.Diff(step.wantAssumedWorkloads, cache.assumedWorkloads); diff != "" {
  1702  				t.Errorf("Unexpected assumed workloads (-want,+got):\n%s", diff)
  1703  			}
  1704  		})
  1705  	}
  1706  }
  1707  
  1708  func TestClusterQueueUsage(t *testing.T) {
  1709  	cq := utiltesting.MakeClusterQueue("foo").
  1710  		ResourceGroup(
  1711  			*utiltesting.MakeFlavorQuotas("default").
  1712  				Resource(corev1.ResourceCPU, "10", "10").
  1713  				Obj(),
  1714  		).
  1715  		ResourceGroup(
  1716  			*utiltesting.MakeFlavorQuotas("model_a").
  1717  				Resource("example.com/gpu", "5", "5").
  1718  				Obj(),
  1719  			*utiltesting.MakeFlavorQuotas("model_b").
  1720  				Resource("example.com/gpu", "5").
  1721  				Obj(),
  1722  		).
  1723  		ResourceGroup(
  1724  			*utiltesting.MakeFlavorQuotas("interconnect_a").
  1725  				Resource("example.com/vf-0", "5", "5").
  1726  				Resource("example.com/vf-1", "5", "5").
  1727  				Resource("example.com/vf-2", "5", "5").
  1728  				Obj(),
  1729  		).
  1730  		Cohort("one").Obj()
  1731  	cqWithOutCohort := cq.DeepCopy()
  1732  	cqWithOutCohort.Spec.Cohort = ""
  1733  	workloads := []kueue.Workload{
  1734  		*utiltesting.MakeWorkload("one", "").
  1735  			Request(corev1.ResourceCPU, "8").
  1736  			Request("example.com/gpu", "5").
  1737  			ReserveQuota(utiltesting.MakeAdmission("foo").Assignment(corev1.ResourceCPU, "default", "8000m").Assignment("example.com/gpu", "model_a", "5").Obj()).
  1738  			Condition(metav1.Condition{Type: kueue.WorkloadAdmitted, Status: metav1.ConditionTrue}).
  1739  			Obj(),
  1740  		*utiltesting.MakeWorkload("two", "").
  1741  			Request(corev1.ResourceCPU, "5").
  1742  			Request("example.com/gpu", "6").
  1743  			ReserveQuota(utiltesting.MakeAdmission("foo").Assignment(corev1.ResourceCPU, "default", "5000m").Assignment("example.com/gpu", "model_b", "6").Obj()).
  1744  			Obj(),
  1745  	}
  1746  	cases := map[string]struct {
  1747  		clusterQueue           *kueue.ClusterQueue
  1748  		workloads              []kueue.Workload
  1749  		wantReservedResources  []kueue.FlavorUsage
  1750  		wantReservingWorkloads int
  1751  		wantUsedResources      []kueue.FlavorUsage
  1752  		wantAdmittedWorkloads  int
  1753  	}{
  1754  		"clusterQueue without cohort; single, admitted no borrowing": {
  1755  			clusterQueue: cqWithOutCohort,
  1756  			workloads:    workloads[:1],
  1757  			wantReservedResources: []kueue.FlavorUsage{
  1758  				{
  1759  					Name: "default",
  1760  					Resources: []kueue.ResourceUsage{{
  1761  						Name:  corev1.ResourceCPU,
  1762  						Total: resource.MustParse("8"),
  1763  					}},
  1764  				},
  1765  				{
  1766  					Name: "model_a",
  1767  					Resources: []kueue.ResourceUsage{{
  1768  						Name:  "example.com/gpu",
  1769  						Total: resource.MustParse("5"),
  1770  					}},
  1771  				},
  1772  				{
  1773  					Name: "model_b",
  1774  					Resources: []kueue.ResourceUsage{{
  1775  						Name: "example.com/gpu",
  1776  					}},
  1777  				},
  1778  				{
  1779  					Name: "interconnect_a",
  1780  					Resources: []kueue.ResourceUsage{
  1781  						{Name: "example.com/vf-0"},
  1782  						{Name: "example.com/vf-1"},
  1783  						{Name: "example.com/vf-2"},
  1784  					},
  1785  				},
  1786  			},
  1787  			wantReservingWorkloads: 1,
  1788  			wantUsedResources: []kueue.FlavorUsage{
  1789  				{
  1790  					Name: "default",
  1791  					Resources: []kueue.ResourceUsage{{
  1792  						Name:  corev1.ResourceCPU,
  1793  						Total: resource.MustParse("8"),
  1794  					}},
  1795  				},
  1796  				{
  1797  					Name: "model_a",
  1798  					Resources: []kueue.ResourceUsage{{
  1799  						Name:  "example.com/gpu",
  1800  						Total: resource.MustParse("5"),
  1801  					}},
  1802  				},
  1803  				{
  1804  					Name: "model_b",
  1805  					Resources: []kueue.ResourceUsage{{
  1806  						Name: "example.com/gpu",
  1807  					}},
  1808  				},
  1809  				{
  1810  					Name: "interconnect_a",
  1811  					Resources: []kueue.ResourceUsage{
  1812  						{Name: "example.com/vf-0"},
  1813  						{Name: "example.com/vf-1"},
  1814  						{Name: "example.com/vf-2"},
  1815  					},
  1816  				},
  1817  			},
  1818  			wantAdmittedWorkloads: 1,
  1819  		},
  1820  		"clusterQueue with cohort; multiple borrowing": {
  1821  			clusterQueue: cq,
  1822  			workloads:    workloads,
  1823  			wantReservedResources: []kueue.FlavorUsage{
  1824  				{
  1825  					Name: "default",
  1826  					Resources: []kueue.ResourceUsage{{
  1827  						Name:     corev1.ResourceCPU,
  1828  						Total:    resource.MustParse("13"),
  1829  						Borrowed: resource.MustParse("3"),
  1830  					}},
  1831  				},
  1832  				{
  1833  					Name: "model_a",
  1834  					Resources: []kueue.ResourceUsage{{
  1835  						Name:  "example.com/gpu",
  1836  						Total: resource.MustParse("5"),
  1837  					}},
  1838  				},
  1839  				{
  1840  					Name: "model_b",
  1841  					Resources: []kueue.ResourceUsage{{
  1842  						Name:     "example.com/gpu",
  1843  						Total:    resource.MustParse("6"),
  1844  						Borrowed: resource.MustParse("1"),
  1845  					}},
  1846  				},
  1847  				{
  1848  					Name: "interconnect_a",
  1849  					Resources: []kueue.ResourceUsage{
  1850  						{Name: "example.com/vf-0"},
  1851  						{Name: "example.com/vf-1"},
  1852  						{Name: "example.com/vf-2"},
  1853  					},
  1854  				},
  1855  			},
  1856  			wantReservingWorkloads: 2,
  1857  			wantUsedResources: []kueue.FlavorUsage{
  1858  				{
  1859  					Name: "default",
  1860  					Resources: []kueue.ResourceUsage{{
  1861  						Name:  corev1.ResourceCPU,
  1862  						Total: resource.MustParse("8"),
  1863  					}},
  1864  				},
  1865  				{
  1866  					Name: "model_a",
  1867  					Resources: []kueue.ResourceUsage{{
  1868  						Name:  "example.com/gpu",
  1869  						Total: resource.MustParse("5"),
  1870  					}},
  1871  				},
  1872  				{
  1873  					Name: "model_b",
  1874  					Resources: []kueue.ResourceUsage{{
  1875  						Name: "example.com/gpu",
  1876  					}},
  1877  				},
  1878  				{
  1879  					Name: "interconnect_a",
  1880  					Resources: []kueue.ResourceUsage{
  1881  						{Name: "example.com/vf-0"},
  1882  						{Name: "example.com/vf-1"},
  1883  						{Name: "example.com/vf-2"},
  1884  					},
  1885  				},
  1886  			},
  1887  			wantAdmittedWorkloads: 1,
  1888  		},
  1889  		"clusterQueue without cohort; multiple borrowing": {
  1890  			clusterQueue: cqWithOutCohort,
  1891  			workloads:    workloads,
  1892  			wantReservedResources: []kueue.FlavorUsage{
  1893  				{
  1894  					Name: "default",
  1895  					Resources: []kueue.ResourceUsage{{
  1896  						Name:     corev1.ResourceCPU,
  1897  						Total:    resource.MustParse("13"),
  1898  						Borrowed: resource.MustParse("0"),
  1899  					}},
  1900  				},
  1901  				{
  1902  					Name: "model_a",
  1903  					Resources: []kueue.ResourceUsage{{
  1904  						Name:  "example.com/gpu",
  1905  						Total: resource.MustParse("5"),
  1906  					}},
  1907  				},
  1908  				{
  1909  					Name: "model_b",
  1910  					Resources: []kueue.ResourceUsage{{
  1911  						Name:     "example.com/gpu",
  1912  						Total:    resource.MustParse("6"),
  1913  						Borrowed: resource.MustParse("0"),
  1914  					}},
  1915  				},
  1916  				{
  1917  					Name: "interconnect_a",
  1918  					Resources: []kueue.ResourceUsage{
  1919  						{Name: "example.com/vf-0"},
  1920  						{Name: "example.com/vf-1"},
  1921  						{Name: "example.com/vf-2"},
  1922  					},
  1923  				},
  1924  			},
  1925  			wantReservingWorkloads: 2,
  1926  			wantUsedResources: []kueue.FlavorUsage{
  1927  				{
  1928  					Name: "default",
  1929  					Resources: []kueue.ResourceUsage{{
  1930  						Name:  corev1.ResourceCPU,
  1931  						Total: resource.MustParse("8"),
  1932  					}},
  1933  				},
  1934  				{
  1935  					Name: "model_a",
  1936  					Resources: []kueue.ResourceUsage{{
  1937  						Name:  "example.com/gpu",
  1938  						Total: resource.MustParse("5"),
  1939  					}},
  1940  				},
  1941  				{
  1942  					Name: "model_b",
  1943  					Resources: []kueue.ResourceUsage{{
  1944  						Name: "example.com/gpu",
  1945  					}},
  1946  				},
  1947  				{
  1948  					Name: "interconnect_a",
  1949  					Resources: []kueue.ResourceUsage{
  1950  						{Name: "example.com/vf-0"},
  1951  						{Name: "example.com/vf-1"},
  1952  						{Name: "example.com/vf-2"},
  1953  					},
  1954  				},
  1955  			},
  1956  			wantAdmittedWorkloads: 1,
  1957  		},
  1958  	}
  1959  	for name, tc := range cases {
  1960  		t.Run(name, func(t *testing.T) {
  1961  			cache := New(utiltesting.NewFakeClient())
  1962  			ctx := context.Background()
  1963  			err := cache.AddClusterQueue(ctx, tc.clusterQueue)
  1964  			if err != nil {
  1965  				t.Fatalf("Adding ClusterQueue: %v", err)
  1966  			}
  1967  			for i := range tc.workloads {
  1968  				w := &tc.workloads[i]
  1969  				if added := cache.AddOrUpdateWorkload(w); !added {
  1970  					t.Fatalf("Workload %s was not added", workload.Key(w))
  1971  				}
  1972  			}
  1973  			stats, err := cache.Usage(tc.clusterQueue)
  1974  			if err != nil {
  1975  				t.Fatalf("Couldn't get usage: %v", err)
  1976  			}
  1977  
  1978  			if diff := cmp.Diff(tc.wantReservedResources, stats.ReservedResources); diff != "" {
  1979  				t.Errorf("Unexpected used reserved resources (-want,+got):\n%s", diff)
  1980  			}
  1981  			if stats.ReservingWorkloads != tc.wantReservingWorkloads {
  1982  				t.Errorf("Got %d reserving workloads, want %d", stats.ReservingWorkloads, tc.wantReservingWorkloads)
  1983  			}
  1984  
  1985  			if diff := cmp.Diff(tc.wantUsedResources, stats.AdmittedResources); diff != "" {
  1986  				t.Errorf("Unexpected used resources (-want,+got):\n%s", diff)
  1987  			}
  1988  			if stats.AdmittedWorkloads != tc.wantAdmittedWorkloads {
  1989  				t.Errorf("Got %d admitted workloads, want %d", stats.AdmittedWorkloads, tc.wantAdmittedWorkloads)
  1990  			}
  1991  		})
  1992  	}
  1993  }
  1994  
  1995  func TestLocalQueueUsage(t *testing.T) {
  1996  	cq := *utiltesting.MakeClusterQueue("foo").
  1997  		ResourceGroup(
  1998  			*utiltesting.MakeFlavorQuotas("default").
  1999  				Resource(corev1.ResourceCPU, "10", "10").Obj(),
  2000  		).
  2001  		ResourceGroup(
  2002  			*utiltesting.MakeFlavorQuotas("model-a").
  2003  				Resource("example.com/gpu", "5").Obj(),
  2004  			*utiltesting.MakeFlavorQuotas("model-b").
  2005  				Resource("example.com/gpu", "5").Obj(),
  2006  		).
  2007  		ResourceGroup(
  2008  			*utiltesting.MakeFlavorQuotas("interconnect-a").
  2009  				Resource("example.com/vf-0", "5", "5").
  2010  				Resource("example.com/vf-1", "5", "5").
  2011  				Resource("example.com/vf-2", "5", "5").
  2012  				Obj(),
  2013  		).
  2014  		Obj()
  2015  	localQueue := *utiltesting.MakeLocalQueue("test", "ns1").
  2016  		ClusterQueue("foo").Obj()
  2017  	cases := map[string]struct {
  2018  		cq             *kueue.ClusterQueue
  2019  		wls            []kueue.Workload
  2020  		wantUsage      []kueue.LocalQueueFlavorUsage
  2021  		inAdmissibleWl sets.Set[string]
  2022  	}{
  2023  		"clusterQueue is missing": {
  2024  			wls: []kueue.Workload{
  2025  				*utiltesting.MakeWorkload("one", "ns1").
  2026  					Queue("test").
  2027  					Request(corev1.ResourceCPU, "5").Obj(),
  2028  			},
  2029  			inAdmissibleWl: sets.New("one"),
  2030  		},
  2031  		"workloads is nothing": {
  2032  			cq: &cq,
  2033  			wantUsage: []kueue.LocalQueueFlavorUsage{
  2034  				{
  2035  					Name: "default",
  2036  					Resources: []kueue.LocalQueueResourceUsage{
  2037  						{
  2038  							Name:  corev1.ResourceCPU,
  2039  							Total: resource.MustParse("0"),
  2040  						},
  2041  					},
  2042  				},
  2043  				{
  2044  					Name: "model-a",
  2045  					Resources: []kueue.LocalQueueResourceUsage{
  2046  						{
  2047  							Name:  "example.com/gpu",
  2048  							Total: resource.MustParse("0"),
  2049  						},
  2050  					},
  2051  				},
  2052  				{
  2053  					Name: "model-b",
  2054  					Resources: []kueue.LocalQueueResourceUsage{
  2055  						{
  2056  							Name:  "example.com/gpu",
  2057  							Total: resource.MustParse("0"),
  2058  						},
  2059  					},
  2060  				},
  2061  				{
  2062  					Name: "interconnect-a",
  2063  					Resources: []kueue.LocalQueueResourceUsage{
  2064  						{Name: "example.com/vf-0"},
  2065  						{Name: "example.com/vf-1"},
  2066  						{Name: "example.com/vf-2"},
  2067  					},
  2068  				},
  2069  			},
  2070  		},
  2071  		"all workloads are admitted": {
  2072  			cq: &cq,
  2073  			wls: []kueue.Workload{
  2074  				*utiltesting.MakeWorkload("one", "ns1").
  2075  					Queue("test").
  2076  					Request(corev1.ResourceCPU, "5").
  2077  					Request("example.com/gpu", "5").
  2078  					ReserveQuota(
  2079  						utiltesting.MakeAdmission("foo").
  2080  							Assignment(corev1.ResourceCPU, "default", "5000m").
  2081  							Assignment("example.com/gpu", "model-a", "5").Obj(),
  2082  					).
  2083  					Obj(),
  2084  				*utiltesting.MakeWorkload("two", "ns1").
  2085  					Queue("test").
  2086  					Request(corev1.ResourceCPU, "3").
  2087  					Request("example.com/gpu", "3").
  2088  					ReserveQuota(
  2089  						utiltesting.MakeAdmission("foo").
  2090  							Assignment(corev1.ResourceCPU, "default", "3000m").
  2091  							Assignment("example.com/gpu", "model-b", "3").Obj(),
  2092  					).
  2093  					Obj(),
  2094  			},
  2095  			wantUsage: []kueue.LocalQueueFlavorUsage{
  2096  				{
  2097  					Name: "default",
  2098  					Resources: []kueue.LocalQueueResourceUsage{
  2099  						{
  2100  							Name:  corev1.ResourceCPU,
  2101  							Total: resource.MustParse("8"),
  2102  						},
  2103  					},
  2104  				},
  2105  				{
  2106  					Name: "model-a",
  2107  					Resources: []kueue.LocalQueueResourceUsage{
  2108  						{
  2109  							Name:  "example.com/gpu",
  2110  							Total: resource.MustParse("5"),
  2111  						},
  2112  					},
  2113  				},
  2114  				{
  2115  					Name: "model-b",
  2116  					Resources: []kueue.LocalQueueResourceUsage{
  2117  						{
  2118  							Name:  "example.com/gpu",
  2119  							Total: resource.MustParse("3"),
  2120  						},
  2121  					},
  2122  				},
  2123  				{
  2124  					Name: "interconnect-a",
  2125  					Resources: []kueue.LocalQueueResourceUsage{
  2126  						{Name: "example.com/vf-0"},
  2127  						{Name: "example.com/vf-1"},
  2128  						{Name: "example.com/vf-2"},
  2129  					},
  2130  				},
  2131  			},
  2132  		},
  2133  		"some workloads are inadmissible": {
  2134  			cq: &cq,
  2135  			wls: []kueue.Workload{
  2136  				*utiltesting.MakeWorkload("one", "ns1").
  2137  					Queue("test").
  2138  					Request(corev1.ResourceCPU, "5").
  2139  					Request("example.com/gpu", "5").
  2140  					ReserveQuota(
  2141  						utiltesting.MakeAdmission("foo").
  2142  							Assignment(corev1.ResourceCPU, "default", "5000m").
  2143  							Assignment("example.com/gpu", "model-a", "5").Obj(),
  2144  					).Obj(),
  2145  				*utiltesting.MakeWorkload("two", "ns1").
  2146  					Queue("test").
  2147  					Request(corev1.ResourceCPU, "100000").
  2148  					Request("example.com/gpu", "3").Obj(),
  2149  			},
  2150  			inAdmissibleWl: sets.New("two"),
  2151  			wantUsage: []kueue.LocalQueueFlavorUsage{
  2152  				{
  2153  					Name: "default",
  2154  					Resources: []kueue.LocalQueueResourceUsage{
  2155  						{
  2156  							Name:  corev1.ResourceCPU,
  2157  							Total: resource.MustParse("5"),
  2158  						},
  2159  					},
  2160  				},
  2161  				{
  2162  					Name: "model-a",
  2163  					Resources: []kueue.LocalQueueResourceUsage{
  2164  						{
  2165  							Name:  "example.com/gpu",
  2166  							Total: resource.MustParse("5"),
  2167  						},
  2168  					},
  2169  				},
  2170  				{
  2171  					Name: "model-b",
  2172  					Resources: []kueue.LocalQueueResourceUsage{
  2173  						{
  2174  							Name:  "example.com/gpu",
  2175  							Total: resource.MustParse("0"),
  2176  						},
  2177  					},
  2178  				},
  2179  				{
  2180  					Name: "interconnect-a",
  2181  					Resources: []kueue.LocalQueueResourceUsage{
  2182  						{Name: "example.com/vf-0"},
  2183  						{Name: "example.com/vf-1"},
  2184  						{Name: "example.com/vf-2"},
  2185  					},
  2186  				},
  2187  			},
  2188  		},
  2189  	}
  2190  	for name, tc := range cases {
  2191  		t.Run(name, func(t *testing.T) {
  2192  			cache := New(utiltesting.NewFakeClient())
  2193  			ctx := context.Background()
  2194  			if tc.cq != nil {
  2195  				if err := cache.AddClusterQueue(ctx, tc.cq); err != nil {
  2196  					t.Fatalf("Adding ClusterQueue: %v", err)
  2197  				}
  2198  			}
  2199  			if err := cache.AddLocalQueue(&localQueue); err != nil {
  2200  				t.Fatalf("Adding LocalQueue: %v", err)
  2201  			}
  2202  			for _, w := range tc.wls {
  2203  				if added := cache.AddOrUpdateWorkload(&w); !added && !tc.inAdmissibleWl.Has(w.Name) {
  2204  					t.Fatalf("Workload %s was not added", workload.Key(&w))
  2205  				}
  2206  			}
  2207  			gotUsage, err := cache.LocalQueueUsage(&localQueue)
  2208  			if err != nil {
  2209  				t.Fatalf("Couldn't get usage for the queue: %v", err)
  2210  			}
  2211  			if diff := cmp.Diff(tc.wantUsage, gotUsage.ReservedResources); diff != "" {
  2212  				t.Errorf("Unexpected used resources for the queue (-want,+got):\n%s", diff)
  2213  			}
  2214  		})
  2215  	}
  2216  }
  2217  
  2218  func TestCacheQueueOperations(t *testing.T) {
  2219  	cqs := []*kueue.ClusterQueue{
  2220  		utiltesting.MakeClusterQueue("foo").
  2221  			ResourceGroup(
  2222  				*utiltesting.MakeFlavorQuotas("spot").
  2223  					Resource("cpu", "10", "10").
  2224  					Resource("memory", "64Gi", "64Gi").Obj(),
  2225  			).ResourceGroup(
  2226  			*utiltesting.MakeFlavorQuotas("model-a").
  2227  				Resource("example.com/gpu", "10", "10").Obj(),
  2228  		).Obj(),
  2229  		utiltesting.MakeClusterQueue("bar").
  2230  			ResourceGroup(
  2231  				*utiltesting.MakeFlavorQuotas("ondemand").
  2232  					Resource("cpu", "5", "5").
  2233  					Resource("memory", "32Gi", "32Gi").Obj(),
  2234  			).ResourceGroup(
  2235  			*utiltesting.MakeFlavorQuotas("model-b").
  2236  				Resource("example.com/gpu", "5", "5").Obj(),
  2237  		).Obj(),
  2238  	}
  2239  	queues := []*kueue.LocalQueue{
  2240  		utiltesting.MakeLocalQueue("alpha", "ns1").ClusterQueue("foo").Obj(),
  2241  		utiltesting.MakeLocalQueue("beta", "ns2").ClusterQueue("foo").Obj(),
  2242  		utiltesting.MakeLocalQueue("gamma", "ns1").ClusterQueue("bar").Obj(),
  2243  	}
  2244  	workloads := []*kueue.Workload{
  2245  		utiltesting.MakeWorkload("job1", "ns1").
  2246  			Queue("alpha").
  2247  			Request("cpu", "2").
  2248  			Request("memory", "8Gi").
  2249  			ReserveQuota(
  2250  				utiltesting.MakeAdmission("foo").
  2251  					Assignment("cpu", "spot", "2").
  2252  					Assignment("memory", "spot", "8Gi").Obj(),
  2253  			).
  2254  			Condition(metav1.Condition{Type: kueue.WorkloadAdmitted, Status: metav1.ConditionTrue}).
  2255  			Obj(),
  2256  		utiltesting.MakeWorkload("job2", "ns2").
  2257  			Queue("beta").
  2258  			Request("example.com/gpu", "2").
  2259  			ReserveQuota(
  2260  				utiltesting.MakeAdmission("foo").
  2261  					Assignment("example.com/gpu", "model-a", "2").Obj(),
  2262  			).
  2263  			Condition(metav1.Condition{Type: kueue.WorkloadAdmitted, Status: metav1.ConditionTrue}).
  2264  			Obj(),
  2265  		utiltesting.MakeWorkload("job3", "ns1").
  2266  			Queue("gamma").
  2267  			Request("cpu", "5").
  2268  			Request("memory", "16Gi").
  2269  			ReserveQuota(
  2270  				utiltesting.MakeAdmission("bar").
  2271  					Assignment("cpu", "ondemand", "5").
  2272  					Assignment("memory", "ondemand", "16Gi").Obj(),
  2273  			).Obj(),
  2274  		utiltesting.MakeWorkload("job4", "ns2").
  2275  			Queue("beta").
  2276  			Request("example.com/gpu", "5").
  2277  			ReserveQuota(
  2278  				utiltesting.MakeAdmission("foo").
  2279  					Assignment("example.com/gpu", "model-a", "5").Obj(),
  2280  			).Obj(),
  2281  	}
  2282  	insertAllClusterQueues := func(ctx context.Context, cl client.Client, cache *Cache) error {
  2283  		for _, cq := range cqs {
  2284  			cq := cq.DeepCopy()
  2285  			if err := cl.Create(ctx, cq); err != nil {
  2286  				return err
  2287  			}
  2288  			if err := cache.AddClusterQueue(ctx, cq); err != nil {
  2289  				return err
  2290  			}
  2291  		}
  2292  		return nil
  2293  	}
  2294  	insertAllQueues := func(ctx context.Context, cl client.Client, cache *Cache) error {
  2295  		for _, q := range queues {
  2296  			q := q.DeepCopy()
  2297  			if err := cl.Create(ctx, q.DeepCopy()); err != nil {
  2298  				return err
  2299  			}
  2300  			if err := cache.AddLocalQueue(q); err != nil {
  2301  				return err
  2302  			}
  2303  		}
  2304  		return nil
  2305  	}
  2306  	insertAllWorkloads := func(ctx context.Context, cl client.Client, cache *Cache) error {
  2307  		for _, wl := range workloads {
  2308  			wl := wl.DeepCopy()
  2309  			if err := cl.Create(ctx, wl); err != nil {
  2310  				return err
  2311  			}
  2312  			cache.AddOrUpdateWorkload(wl)
  2313  		}
  2314  		return nil
  2315  	}
  2316  	cacheLocalQueuesAfterInsertingAll := map[string]*queue{
  2317  		"ns1/alpha": {
  2318  			key:                "ns1/alpha",
  2319  			reservingWorkloads: 1,
  2320  			admittedWorkloads:  1,
  2321  			usage: FlavorResourceQuantities{
  2322  				"spot": {
  2323  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("2")),
  2324  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("8Gi")),
  2325  				},
  2326  				"model-a": {
  2327  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2328  				},
  2329  			},
  2330  			admittedUsage: FlavorResourceQuantities{
  2331  				"spot": {
  2332  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("2")),
  2333  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("8Gi")),
  2334  				},
  2335  				"model-a": {
  2336  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2337  				},
  2338  			},
  2339  		},
  2340  		"ns2/beta": {
  2341  			key:                "ns2/beta",
  2342  			reservingWorkloads: 2,
  2343  			admittedWorkloads:  1,
  2344  			usage: FlavorResourceQuantities{
  2345  				"spot": {
  2346  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2347  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2348  				},
  2349  				"model-a": {
  2350  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("7")),
  2351  				},
  2352  			},
  2353  			admittedUsage: FlavorResourceQuantities{
  2354  				"spot": {
  2355  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2356  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2357  				},
  2358  				"model-a": {
  2359  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("2")),
  2360  				},
  2361  			},
  2362  		},
  2363  		"ns1/gamma": {
  2364  			key:                "ns1/gamma",
  2365  			reservingWorkloads: 1,
  2366  			admittedWorkloads:  0,
  2367  			usage: FlavorResourceQuantities{
  2368  				"ondemand": {
  2369  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("5")),
  2370  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("16Gi")),
  2371  				},
  2372  				"model-b": {
  2373  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2374  				},
  2375  			},
  2376  			admittedUsage: FlavorResourceQuantities{
  2377  				"ondemand": {
  2378  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2379  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2380  				},
  2381  				"model-b": {
  2382  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2383  				},
  2384  			},
  2385  		},
  2386  	}
  2387  	cacheLocalQueuesAfterInsertingCqAndQ := map[string]*queue{
  2388  		"ns1/alpha": {
  2389  			key:                "ns1/alpha",
  2390  			reservingWorkloads: 0,
  2391  			admittedWorkloads:  0,
  2392  			usage: FlavorResourceQuantities{
  2393  				"spot": {
  2394  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2395  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2396  				},
  2397  				"model-a": {
  2398  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2399  				},
  2400  			},
  2401  			admittedUsage: FlavorResourceQuantities{
  2402  				"spot": {
  2403  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2404  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2405  				},
  2406  				"model-a": {
  2407  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2408  				},
  2409  			},
  2410  		},
  2411  		"ns2/beta": {
  2412  			key:                "ns2/beta",
  2413  			reservingWorkloads: 0,
  2414  			admittedWorkloads:  0,
  2415  			usage: FlavorResourceQuantities{
  2416  				"spot": {
  2417  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2418  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2419  				},
  2420  				"model-a": {
  2421  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2422  				},
  2423  			},
  2424  			admittedUsage: FlavorResourceQuantities{
  2425  				"spot": {
  2426  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2427  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2428  				},
  2429  				"model-a": {
  2430  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2431  				},
  2432  			},
  2433  		},
  2434  		"ns1/gamma": {
  2435  			key:                "ns1/gamma",
  2436  			reservingWorkloads: 0,
  2437  			admittedWorkloads:  0,
  2438  			usage: FlavorResourceQuantities{
  2439  				"ondemand": {
  2440  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2441  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2442  				},
  2443  				"model-b": {
  2444  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2445  				},
  2446  			},
  2447  			admittedUsage: FlavorResourceQuantities{
  2448  				"ondemand": {
  2449  					corev1.ResourceCPU:    workload.ResourceValue(corev1.ResourceCPU, resource.MustParse("0")),
  2450  					corev1.ResourceMemory: workload.ResourceValue(corev1.ResourceMemory, resource.MustParse("0")),
  2451  				},
  2452  				"model-b": {
  2453  					"example.com/gpu": workload.ResourceValue("example.com/gpu", resource.MustParse("0")),
  2454  				},
  2455  			},
  2456  		},
  2457  	}
  2458  	cases := map[string]struct {
  2459  		ops             []func(context.Context, client.Client, *Cache) error
  2460  		wantLocalQueues map[string]*queue
  2461  	}{
  2462  		"insert cqs, queues, workloads": {
  2463  			ops: []func(ctx context.Context, cl client.Client, cache *Cache) error{
  2464  				insertAllClusterQueues,
  2465  				insertAllQueues,
  2466  				insertAllWorkloads,
  2467  			},
  2468  			wantLocalQueues: cacheLocalQueuesAfterInsertingAll,
  2469  		},
  2470  		"insert cqs, workloads but no queues": {
  2471  			ops: []func(context.Context, client.Client, *Cache) error{
  2472  				insertAllClusterQueues,
  2473  				insertAllWorkloads,
  2474  			},
  2475  			wantLocalQueues: map[string]*queue{},
  2476  		},
  2477  		"insert queues, workloads but no cqs": {
  2478  			ops: []func(context.Context, client.Client, *Cache) error{
  2479  				insertAllQueues,
  2480  				insertAllWorkloads,
  2481  			},
  2482  			wantLocalQueues: map[string]*queue{},
  2483  		},
  2484  		"insert queues last": {
  2485  			ops: []func(context.Context, client.Client, *Cache) error{
  2486  				insertAllClusterQueues,
  2487  				insertAllWorkloads,
  2488  				insertAllQueues,
  2489  			},
  2490  			wantLocalQueues: cacheLocalQueuesAfterInsertingAll,
  2491  		},
  2492  		"insert cqs last": {
  2493  			ops: []func(context.Context, client.Client, *Cache) error{
  2494  				insertAllQueues,
  2495  				insertAllWorkloads,
  2496  				insertAllClusterQueues,
  2497  			},
  2498  			wantLocalQueues: cacheLocalQueuesAfterInsertingAll,
  2499  		},
  2500  		"assume": {
  2501  			ops: []func(context.Context, client.Client, *Cache) error{
  2502  				insertAllClusterQueues,
  2503  				insertAllQueues,
  2504  				func(ctx context.Context, cl client.Client, cache *Cache) error {
  2505  					wl := workloads[0].DeepCopy()
  2506  					if err := cl.Create(ctx, wl); err != nil {
  2507  						return err
  2508  					}
  2509  					return cache.AssumeWorkload(wl)
  2510  				},
  2511  			},
  2512  			wantLocalQueues: map[string]*queue{
  2513  				"ns1/alpha": cacheLocalQueuesAfterInsertingAll["ns1/alpha"],
  2514  				"ns2/beta":  cacheLocalQueuesAfterInsertingCqAndQ["ns2/beta"],
  2515  				"ns1/gamma": cacheLocalQueuesAfterInsertingCqAndQ["ns1/gamma"],
  2516  			},
  2517  		},
  2518  		"assume and forget": {
  2519  			ops: []func(context.Context, client.Client, *Cache) error{
  2520  				insertAllClusterQueues,
  2521  				insertAllQueues,
  2522  				func(ctx context.Context, cl client.Client, cache *Cache) error {
  2523  					wl := workloads[0].DeepCopy()
  2524  					if err := cl.Create(ctx, wl); err != nil {
  2525  						return err
  2526  					}
  2527  					if err := cache.AssumeWorkload(wl); err != nil {
  2528  						return err
  2529  					}
  2530  					return cache.ForgetWorkload(wl)
  2531  				},
  2532  			},
  2533  			wantLocalQueues: map[string]*queue{
  2534  				"ns1/alpha": cacheLocalQueuesAfterInsertingCqAndQ["ns1/alpha"],
  2535  				"ns2/beta":  cacheLocalQueuesAfterInsertingCqAndQ["ns2/beta"],
  2536  				"ns1/gamma": cacheLocalQueuesAfterInsertingCqAndQ["ns1/gamma"],
  2537  			},
  2538  		},
  2539  		"delete workload": {
  2540  			ops: []func(ctx context.Context, cl client.Client, cache *Cache) error{
  2541  				insertAllClusterQueues,
  2542  				insertAllQueues,
  2543  				insertAllWorkloads,
  2544  				func(ctx context.Context, cl client.Client, cache *Cache) error {
  2545  					return cache.DeleteWorkload(workloads[0])
  2546  				},
  2547  			},
  2548  			wantLocalQueues: map[string]*queue{
  2549  				"ns1/alpha": cacheLocalQueuesAfterInsertingCqAndQ["ns1/alpha"],
  2550  				"ns2/beta":  cacheLocalQueuesAfterInsertingAll["ns2/beta"],
  2551  				"ns1/gamma": cacheLocalQueuesAfterInsertingAll["ns1/gamma"],
  2552  			},
  2553  		},
  2554  		"delete cq": {
  2555  			ops: []func(ctx context.Context, cl client.Client, cache *Cache) error{
  2556  				insertAllClusterQueues,
  2557  				insertAllQueues,
  2558  				insertAllWorkloads,
  2559  				func(ctx context.Context, cl client.Client, cache *Cache) error {
  2560  					cache.DeleteClusterQueue(cqs[0])
  2561  					return nil
  2562  				},
  2563  			},
  2564  			wantLocalQueues: map[string]*queue{
  2565  				"ns1/gamma": cacheLocalQueuesAfterInsertingAll["ns1/gamma"],
  2566  			},
  2567  		},
  2568  		"delete queue": {
  2569  			ops: []func(ctx context.Context, cl client.Client, cache *Cache) error{
  2570  				insertAllClusterQueues,
  2571  				insertAllQueues,
  2572  				insertAllWorkloads,
  2573  				func(ctx context.Context, cl client.Client, cache *Cache) error {
  2574  					cache.DeleteLocalQueue(queues[0])
  2575  					return nil
  2576  				},
  2577  			},
  2578  			wantLocalQueues: map[string]*queue{
  2579  				"ns2/beta":  cacheLocalQueuesAfterInsertingAll["ns2/beta"],
  2580  				"ns1/gamma": cacheLocalQueuesAfterInsertingAll["ns1/gamma"],
  2581  			},
  2582  		},
  2583  		// Not tested: changing a workload's queue and changing a queue's cluster queue.
  2584  		// These operations should not be allowed by the webhook.
  2585  	}
  2586  	for name, tc := range cases {
  2587  		t.Run(name, func(t *testing.T) {
  2588  			cl := utiltesting.NewFakeClient()
  2589  			cache := New(cl)
  2590  			ctx := context.Background()
  2591  			for i, op := range tc.ops {
  2592  				if err := op(ctx, cl, cache); err != nil {
  2593  					t.Fatalf("Running op %d: %v", i, err)
  2594  				}
  2595  			}
  2596  			cacheQueues := make(map[string]*queue)
  2597  			for _, cacheCQ := range cache.clusterQueues {
  2598  				for qKey, cacheQ := range cacheCQ.localQueues {
  2599  					if _, ok := cacheQueues[qKey]; ok {
  2600  						t.Fatalf("The cache have a duplicated localQueue %q across multiple clusterQueues", qKey)
  2601  					}
  2602  					cacheQueues[qKey] = cacheQ
  2603  				}
  2604  			}
  2605  			if diff := cmp.Diff(tc.wantLocalQueues, cacheQueues, cmp.AllowUnexported(queue{})); diff != "" {
  2606  				t.Errorf("Unexpected localQueues (-want,+got):\n%s", diff)
  2607  			}
  2608  		})
  2609  	}
  2610  }
  2611  
  2612  func TestClusterQueuesUsingFlavor(t *testing.T) {
  2613  	x86Rf := utiltesting.MakeResourceFlavor("x86").Obj()
  2614  	aarch64Rf := utiltesting.MakeResourceFlavor("aarch64").Obj()
  2615  	fooCq := utiltesting.MakeClusterQueue("fooCq").
  2616  		ResourceGroup(
  2617  			*utiltesting.MakeFlavorQuotas("x86").Resource("cpu", "5").Obj()).
  2618  		Obj()
  2619  	barCq := utiltesting.MakeClusterQueue("barCq").Obj()
  2620  	fizzCq := utiltesting.MakeClusterQueue("fizzCq").
  2621  		ResourceGroup(
  2622  			*utiltesting.MakeFlavorQuotas("x86").Resource("cpu", "5").Obj(),
  2623  			*utiltesting.MakeFlavorQuotas("aarch64").Resource("cpu", "3").Obj(),
  2624  		).
  2625  		Obj()
  2626  
  2627  	tests := []struct {
  2628  		name                       string
  2629  		clusterQueues              []*kueue.ClusterQueue
  2630  		wantInUseClusterQueueNames []string
  2631  	}{
  2632  		{
  2633  			name: "single clusterQueue with flavor in use",
  2634  			clusterQueues: []*kueue.ClusterQueue{
  2635  				fooCq,
  2636  			},
  2637  			wantInUseClusterQueueNames: []string{fooCq.Name},
  2638  		},
  2639  		{
  2640  			name: "single clusterQueue with no flavor",
  2641  			clusterQueues: []*kueue.ClusterQueue{
  2642  				barCq,
  2643  			},
  2644  		},
  2645  		{
  2646  			name: "multiple clusterQueues with flavor in use",
  2647  			clusterQueues: []*kueue.ClusterQueue{
  2648  				fooCq,
  2649  				barCq,
  2650  				fizzCq,
  2651  			},
  2652  			wantInUseClusterQueueNames: []string{fooCq.Name, fizzCq.Name},
  2653  		},
  2654  	}
  2655  	for _, tc := range tests {
  2656  		t.Run(tc.name, func(t *testing.T) {
  2657  			cache := New(utiltesting.NewFakeClient())
  2658  			cache.AddOrUpdateResourceFlavor(x86Rf)
  2659  			cache.AddOrUpdateResourceFlavor(aarch64Rf)
  2660  
  2661  			for _, cq := range tc.clusterQueues {
  2662  				if err := cache.AddClusterQueue(context.Background(), cq); err != nil {
  2663  					t.Errorf("failed to add clusterQueue %s", cq.Name)
  2664  				}
  2665  			}
  2666  
  2667  			cqs := cache.ClusterQueuesUsingFlavor("x86")
  2668  			if diff := cmp.Diff(tc.wantInUseClusterQueueNames, cqs, cmpopts.SortSlices(func(a, b string) bool {
  2669  				return a < b
  2670  			})); len(diff) != 0 {
  2671  				t.Errorf("Unexpected flavor is in use by clusterQueues (-want,+got):\n%s", diff)
  2672  			}
  2673  		})
  2674  	}
  2675  }
  2676  
  2677  func TestMatchingClusterQueues(t *testing.T) {
  2678  	clusterQueues := []*kueue.ClusterQueue{
  2679  		utiltesting.MakeClusterQueue("matching1").
  2680  			NamespaceSelector(&metav1.LabelSelector{}).Obj(),
  2681  		utiltesting.MakeClusterQueue("not-matching").
  2682  			NamespaceSelector(nil).Obj(),
  2683  		utiltesting.MakeClusterQueue("matching2").
  2684  			NamespaceSelector(&metav1.LabelSelector{
  2685  				MatchExpressions: []metav1.LabelSelectorRequirement{
  2686  					{
  2687  						Key:      "dep",
  2688  						Operator: metav1.LabelSelectorOpIn,
  2689  						Values:   []string{"eng"},
  2690  					},
  2691  				},
  2692  			}).Obj(),
  2693  	}
  2694  	wantCQs := sets.New("matching1", "matching2")
  2695  
  2696  	cache := New(utiltesting.NewFakeClient())
  2697  	for _, cq := range clusterQueues {
  2698  		if err := cache.AddClusterQueue(context.Background(), cq); err != nil {
  2699  			t.Errorf("failed to add clusterQueue %s", cq.Name)
  2700  		}
  2701  	}
  2702  
  2703  	gotCQs := cache.MatchingClusterQueues(map[string]string{"dep": "eng"})
  2704  	if diff := cmp.Diff(wantCQs, gotCQs); diff != "" {
  2705  		t.Errorf("Wrong ClusterQueues (-want,+got):\n%s", diff)
  2706  	}
  2707  }
  2708  
  2709  // TestWaitForPodsReadyCancelled ensures that the WaitForPodsReady call does not block when the context is closed.
  2710  func TestWaitForPodsReadyCancelled(t *testing.T) {
  2711  	cache := New(utiltesting.NewFakeClient(), WithPodsReadyTracking(true))
  2712  	ctx, cancel := context.WithCancel(context.Background())
  2713  	log := ctrl.LoggerFrom(ctx)
  2714  
  2715  	go cache.CleanUpOnContext(ctx)
  2716  
  2717  	cq := kueue.ClusterQueue{
  2718  		ObjectMeta: metav1.ObjectMeta{Name: "one"},
  2719  	}
  2720  	if err := cache.AddClusterQueue(ctx, &cq); err != nil {
  2721  		t.Fatalf("Failed adding clusterQueue: %v", err)
  2722  	}
  2723  
  2724  	wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2725  		ClusterQueue: "one",
  2726  	}).Obj()
  2727  	if err := cache.AssumeWorkload(wl); err != nil {
  2728  		t.Fatalf("Failed assuming the workload to block the further admission: %v", err)
  2729  	}
  2730  
  2731  	if cache.PodsReadyForAllAdmittedWorkloads(log) {
  2732  		t.Fatalf("Unexpected that all admitted workloads are in PodsReady condition")
  2733  	}
  2734  
  2735  	// cancel the context so that the WaitForPodsReady is returns
  2736  	go cancel()
  2737  
  2738  	cache.WaitForPodsReady(ctx)
  2739  }
  2740  
  2741  // TestCachePodsReadyForAllAdmittedWorkloads verifies the condition used to determine whether to wait
  2742  func TestCachePodsReadyForAllAdmittedWorkloads(t *testing.T) {
  2743  	clusterQueues := []kueue.ClusterQueue{
  2744  		{
  2745  			ObjectMeta: metav1.ObjectMeta{Name: "one"},
  2746  		},
  2747  		{
  2748  			ObjectMeta: metav1.ObjectMeta{Name: "two"},
  2749  		},
  2750  	}
  2751  
  2752  	cl := utiltesting.NewFakeClient()
  2753  
  2754  	tests := []struct {
  2755  		name      string
  2756  		setup     func(cache *Cache) error
  2757  		operation func(cache *Cache) error
  2758  		wantReady bool
  2759  	}{
  2760  		{
  2761  			name:      "empty cache",
  2762  			operation: func(cache *Cache) error { return nil },
  2763  			wantReady: true,
  2764  		},
  2765  		{
  2766  			name: "add Workload without PodsReady condition",
  2767  			operation: func(cache *Cache) error {
  2768  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2769  					ClusterQueue: "one",
  2770  				}).Obj()
  2771  				cache.AddOrUpdateWorkload(wl)
  2772  				return nil
  2773  			},
  2774  			wantReady: false,
  2775  		},
  2776  		{
  2777  			name: "add Workload with PodsReady=False",
  2778  			operation: func(cache *Cache) error {
  2779  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2780  					ClusterQueue: "one",
  2781  				}).Condition(metav1.Condition{
  2782  					Type:   kueue.WorkloadPodsReady,
  2783  					Status: metav1.ConditionFalse,
  2784  				}).Obj()
  2785  				cache.AddOrUpdateWorkload(wl)
  2786  				return nil
  2787  			},
  2788  			wantReady: false,
  2789  		},
  2790  		{
  2791  			name: "add Workload with PodsReady=True",
  2792  			operation: func(cache *Cache) error {
  2793  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2794  					ClusterQueue: "one",
  2795  				}).Condition(metav1.Condition{
  2796  					Type:   kueue.WorkloadPodsReady,
  2797  					Status: metav1.ConditionTrue,
  2798  				}).Obj()
  2799  				cache.AddOrUpdateWorkload(wl)
  2800  				return nil
  2801  			},
  2802  			wantReady: true,
  2803  		},
  2804  		{
  2805  			name: "assume Workload without PodsReady condition",
  2806  			operation: func(cache *Cache) error {
  2807  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2808  					ClusterQueue: "one",
  2809  				}).Obj()
  2810  				return cache.AssumeWorkload(wl)
  2811  			},
  2812  			wantReady: false,
  2813  		},
  2814  		{
  2815  			name: "assume Workload with PodsReady=False",
  2816  			operation: func(cache *Cache) error {
  2817  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2818  					ClusterQueue: "one",
  2819  				}).Condition(metav1.Condition{
  2820  					Type:   kueue.WorkloadPodsReady,
  2821  					Status: metav1.ConditionFalse,
  2822  				}).Obj()
  2823  				return cache.AssumeWorkload(wl)
  2824  			},
  2825  			wantReady: false,
  2826  		},
  2827  		{
  2828  			name: "assume Workload with PodsReady=True",
  2829  			operation: func(cache *Cache) error {
  2830  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2831  					ClusterQueue: "one",
  2832  				}).Condition(metav1.Condition{
  2833  					Type:   kueue.WorkloadPodsReady,
  2834  					Status: metav1.ConditionTrue,
  2835  				}).Obj()
  2836  				return cache.AssumeWorkload(wl)
  2837  			},
  2838  			wantReady: true,
  2839  		},
  2840  		{
  2841  			name: "update workload to have PodsReady=True",
  2842  			setup: func(cache *Cache) error {
  2843  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2844  					ClusterQueue: "one",
  2845  				}).Obj()
  2846  				cache.AddOrUpdateWorkload(wl)
  2847  				return nil
  2848  			},
  2849  			operation: func(cache *Cache) error {
  2850  				wl := cache.clusterQueues["one"].Workloads["/a"].Obj
  2851  				newWl := wl.DeepCopy()
  2852  				apimeta.SetStatusCondition(&newWl.Status.Conditions, metav1.Condition{
  2853  					Type:   kueue.WorkloadPodsReady,
  2854  					Status: metav1.ConditionTrue,
  2855  				})
  2856  				return cache.UpdateWorkload(wl, newWl)
  2857  			},
  2858  			wantReady: true,
  2859  		},
  2860  		{
  2861  			name: "update workload to have PodsReady=False",
  2862  			setup: func(cache *Cache) error {
  2863  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2864  					ClusterQueue: "one",
  2865  				}).Condition(metav1.Condition{
  2866  					Type:   kueue.WorkloadPodsReady,
  2867  					Status: metav1.ConditionTrue,
  2868  				}).Obj()
  2869  				cache.AddOrUpdateWorkload(wl)
  2870  				return nil
  2871  			},
  2872  			operation: func(cache *Cache) error {
  2873  				wl := cache.clusterQueues["one"].Workloads["/a"].Obj
  2874  				newWl := wl.DeepCopy()
  2875  				apimeta.SetStatusCondition(&newWl.Status.Conditions, metav1.Condition{
  2876  					Type:   kueue.WorkloadPodsReady,
  2877  					Status: metav1.ConditionFalse,
  2878  				})
  2879  				return cache.UpdateWorkload(wl, newWl)
  2880  			},
  2881  			wantReady: false,
  2882  		},
  2883  		{
  2884  			name: "assume second workload without PodsReady",
  2885  			setup: func(cache *Cache) error {
  2886  				wl1 := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2887  					ClusterQueue: "one",
  2888  				}).Condition(metav1.Condition{
  2889  					Type:   kueue.WorkloadPodsReady,
  2890  					Status: metav1.ConditionTrue,
  2891  				}).Obj()
  2892  				cache.AddOrUpdateWorkload(wl1)
  2893  				return nil
  2894  			},
  2895  			operation: func(cache *Cache) error {
  2896  				wl2 := utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{
  2897  					ClusterQueue: "two",
  2898  				}).Obj()
  2899  				return cache.AssumeWorkload(wl2)
  2900  			},
  2901  			wantReady: false,
  2902  		},
  2903  		{
  2904  			name: "update second workload to have PodsReady=True",
  2905  			setup: func(cache *Cache) error {
  2906  				wl1 := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2907  					ClusterQueue: "one",
  2908  				}).Condition(metav1.Condition{
  2909  					Type:   kueue.WorkloadPodsReady,
  2910  					Status: metav1.ConditionTrue,
  2911  				}).Obj()
  2912  				cache.AddOrUpdateWorkload(wl1)
  2913  				wl2 := utiltesting.MakeWorkload("b", "").ReserveQuota(&kueue.Admission{
  2914  					ClusterQueue: "two",
  2915  				}).Obj()
  2916  				cache.AddOrUpdateWorkload(wl2)
  2917  				return nil
  2918  			},
  2919  			operation: func(cache *Cache) error {
  2920  				wl2 := cache.clusterQueues["two"].Workloads["/b"].Obj
  2921  				newWl2 := wl2.DeepCopy()
  2922  				apimeta.SetStatusCondition(&newWl2.Status.Conditions, metav1.Condition{
  2923  					Type:   kueue.WorkloadPodsReady,
  2924  					Status: metav1.ConditionTrue,
  2925  				})
  2926  				return cache.UpdateWorkload(wl2, newWl2)
  2927  			},
  2928  			wantReady: true,
  2929  		},
  2930  		{
  2931  			name: "delete workload with PodsReady=False",
  2932  			setup: func(cache *Cache) error {
  2933  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2934  					ClusterQueue: "one",
  2935  				}).Condition(metav1.Condition{
  2936  					Type:   kueue.WorkloadPodsReady,
  2937  					Status: metav1.ConditionFalse,
  2938  				}).Obj()
  2939  				cache.AddOrUpdateWorkload(wl)
  2940  				return nil
  2941  			},
  2942  			operation: func(cache *Cache) error {
  2943  				wl := cache.clusterQueues["one"].Workloads["/a"].Obj
  2944  				return cache.DeleteWorkload(wl)
  2945  			},
  2946  			wantReady: true,
  2947  		},
  2948  		{
  2949  			name: "forget workload with PodsReady=False",
  2950  			setup: func(cache *Cache) error {
  2951  				wl := utiltesting.MakeWorkload("a", "").ReserveQuota(&kueue.Admission{
  2952  					ClusterQueue: "one",
  2953  				}).Condition(metav1.Condition{
  2954  					Type:   kueue.WorkloadPodsReady,
  2955  					Status: metav1.ConditionFalse,
  2956  				}).Obj()
  2957  				return cache.AssumeWorkload(wl)
  2958  			},
  2959  			operation: func(cache *Cache) error {
  2960  				wl := cache.clusterQueues["one"].Workloads["/a"].Obj
  2961  				return cache.ForgetWorkload(wl)
  2962  			},
  2963  			wantReady: true,
  2964  		},
  2965  	}
  2966  	for _, tc := range tests {
  2967  		t.Run(tc.name, func(t *testing.T) {
  2968  			cache := New(cl, WithPodsReadyTracking(true))
  2969  			ctx := context.Background()
  2970  			log := ctrl.LoggerFrom(ctx)
  2971  
  2972  			for _, c := range clusterQueues {
  2973  				if err := cache.AddClusterQueue(ctx, &c); err != nil {
  2974  					t.Fatalf("Failed adding clusterQueue: %v", err)
  2975  				}
  2976  			}
  2977  			if tc.setup != nil {
  2978  				if err := tc.setup(cache); err != nil {
  2979  					t.Errorf("Unexpected error during setup: %q", err)
  2980  				}
  2981  			}
  2982  			if err := tc.operation(cache); err != nil {
  2983  				t.Errorf("Unexpected error during operation: %q", err)
  2984  			}
  2985  			gotReady := cache.PodsReadyForAllAdmittedWorkloads(log)
  2986  			if diff := cmp.Diff(tc.wantReady, gotReady); diff != "" {
  2987  				t.Errorf("Unexpected response about workloads without pods ready (-want,+got):\n%s", diff)
  2988  			}
  2989  			// verify that the WaitForPodsReady is non-blocking when podsReadyForAllAdmittedWorkloads returns true
  2990  			if gotReady {
  2991  				cache.WaitForPodsReady(ctx)
  2992  			}
  2993  		})
  2994  	}
  2995  }
  2996  
  2997  // TestIsAssumedOrAdmittedCheckWorkload verifies if workload is in Assumed map from cache or if it is Admitted in one ClusterQueue
  2998  func TestIsAssumedOrAdmittedCheckWorkload(t *testing.T) {
  2999  	tests := []struct {
  3000  		name     string
  3001  		cache    *Cache
  3002  		workload workload.Info
  3003  		expected bool
  3004  	}{
  3005  		{
  3006  			name: "Workload Is Assumed and not Admitted",
  3007  			cache: &Cache{
  3008  				assumedWorkloads: map[string]string{"workload_namespace/workload_name": "test", "test2": "test2"},
  3009  			},
  3010  			workload: workload.Info{
  3011  				ClusterQueue: "ClusterQueue1",
  3012  				Obj: &kueue.Workload{
  3013  					ObjectMeta: metav1.ObjectMeta{
  3014  						Name:      "workload_name",
  3015  						Namespace: "workload_namespace",
  3016  					},
  3017  				},
  3018  			},
  3019  			expected: true,
  3020  		}, {
  3021  			name: "Workload Is not Assumed but is Admitted",
  3022  			cache: &Cache{
  3023  				clusterQueues: map[string]*ClusterQueue{
  3024  					"ClusterQueue1": {
  3025  						Name: "ClusterQueue1",
  3026  						Workloads: map[string]*workload.Info{"workload_namespace/workload_name": {
  3027  							Obj: &kueue.Workload{
  3028  								ObjectMeta: metav1.ObjectMeta{
  3029  									Name:      "workload_name",
  3030  									Namespace: "workload_namespace",
  3031  								},
  3032  							},
  3033  						}},
  3034  					}},
  3035  			},
  3036  
  3037  			workload: workload.Info{
  3038  				ClusterQueue: "ClusterQueue1",
  3039  				Obj: &kueue.Workload{
  3040  					ObjectMeta: metav1.ObjectMeta{
  3041  						Name:      "workload_name",
  3042  						Namespace: "workload_namespace",
  3043  					},
  3044  				},
  3045  			},
  3046  			expected: true,
  3047  		}, {
  3048  			name: "Workload Is Assumed and Admitted",
  3049  			cache: &Cache{
  3050  				clusterQueues: map[string]*ClusterQueue{
  3051  					"ClusterQueue1": {
  3052  						Name: "ClusterQueue1",
  3053  						Workloads: map[string]*workload.Info{"workload_namespace/workload_name": {
  3054  							Obj: &kueue.Workload{
  3055  								ObjectMeta: metav1.ObjectMeta{
  3056  									Name:      "workload_name",
  3057  									Namespace: "workload_namespace",
  3058  								},
  3059  							},
  3060  						}},
  3061  					}},
  3062  				assumedWorkloads: map[string]string{"workload_namespace/workload_name": "test", "test2": "test2"},
  3063  			},
  3064  			workload: workload.Info{
  3065  				ClusterQueue: "ClusterQueue1",
  3066  				Obj: &kueue.Workload{
  3067  					ObjectMeta: metav1.ObjectMeta{
  3068  						Name:      "workload_name",
  3069  						Namespace: "workload_namespace",
  3070  					},
  3071  				},
  3072  			},
  3073  			expected: true,
  3074  		}, {
  3075  			name: "Workload Is not Assumed and is not Admitted",
  3076  			cache: &Cache{
  3077  				clusterQueues: map[string]*ClusterQueue{
  3078  					"ClusterQueue1": {
  3079  						Name: "ClusterQueue1",
  3080  						Workloads: map[string]*workload.Info{"workload_namespace2/workload_name2": {
  3081  							Obj: &kueue.Workload{
  3082  								ObjectMeta: metav1.ObjectMeta{
  3083  									Name:      "workload_name2",
  3084  									Namespace: "workload_namespace2",
  3085  								},
  3086  							},
  3087  						}},
  3088  					}},
  3089  			},
  3090  			workload: workload.Info{
  3091  				ClusterQueue: "ClusterQueue1",
  3092  				Obj: &kueue.Workload{
  3093  					ObjectMeta: metav1.ObjectMeta{
  3094  						Name:      "workload_name",
  3095  						Namespace: "workload_namespace",
  3096  					},
  3097  				},
  3098  			},
  3099  			expected: false,
  3100  		},
  3101  	}
  3102  	for _, tc := range tests {
  3103  		t.Run(tc.name, func(t *testing.T) {
  3104  			if tc.cache.IsAssumedOrAdmittedWorkload(tc.workload) != tc.expected {
  3105  				t.Error("Unexpected response")
  3106  			}
  3107  		})
  3108  	}
  3109  }
  3110  
  3111  func messageOrEmpty(err error) string {
  3112  	if err == nil {
  3113  		return ""
  3114  	}
  3115  	return err.Error()
  3116  }
  3117  
  3118  func TestClusterQueuesUsingAdmissionChecks(t *testing.T) {
  3119  	admissionCheck1 := utiltesting.MakeAdmissionCheck("ac1").Obj()
  3120  	admissionCheck2 := utiltesting.MakeAdmissionCheck("ac2").Obj()
  3121  	fooCq := utiltesting.MakeClusterQueue("fooCq").
  3122  		AdmissionChecks(admissionCheck1.Name).
  3123  		Obj()
  3124  	barCq := utiltesting.MakeClusterQueue("barCq").Obj()
  3125  	fizzCq := utiltesting.MakeClusterQueue("fizzCq").
  3126  		AdmissionChecks(admissionCheck1.Name, admissionCheck2.Name).
  3127  		Obj()
  3128  
  3129  	cases := map[string]struct {
  3130  		clusterQueues              []*kueue.ClusterQueue
  3131  		wantInUseClusterQueueNames []string
  3132  	}{
  3133  		"single clusterQueue with check in use": {
  3134  			clusterQueues: []*kueue.ClusterQueue{
  3135  				fooCq,
  3136  			},
  3137  			wantInUseClusterQueueNames: []string{fooCq.Name},
  3138  		},
  3139  		"single clusterQueue with no checks": {
  3140  			clusterQueues: []*kueue.ClusterQueue{
  3141  				barCq,
  3142  			},
  3143  		},
  3144  		"multiple clusterQueues with checks in use": {
  3145  			clusterQueues: []*kueue.ClusterQueue{
  3146  				fooCq,
  3147  				barCq,
  3148  				fizzCq,
  3149  			},
  3150  			wantInUseClusterQueueNames: []string{fooCq.Name, fizzCq.Name},
  3151  		},
  3152  	}
  3153  	for name, tc := range cases {
  3154  		t.Run(name, func(t *testing.T) {
  3155  			cache := New(utiltesting.NewFakeClient())
  3156  			cache.AddOrUpdateAdmissionCheck(admissionCheck1)
  3157  			cache.AddOrUpdateAdmissionCheck(admissionCheck2)
  3158  
  3159  			for _, cq := range tc.clusterQueues {
  3160  				if err := cache.AddClusterQueue(context.Background(), cq); err != nil {
  3161  					t.Errorf("failed to add clusterQueue %s", cq.Name)
  3162  				}
  3163  			}
  3164  
  3165  			cqs := cache.ClusterQueuesUsingAdmissionCheck(admissionCheck1.Name)
  3166  			if diff := cmp.Diff(tc.wantInUseClusterQueueNames, cqs, cmpopts.SortSlices(func(a, b string) bool {
  3167  				return a < b
  3168  			})); len(diff) != 0 {
  3169  				t.Errorf("Unexpected flavor is in use by clusterQueues (-want,+got):\n%s", diff)
  3170  			}
  3171  		})
  3172  	}
  3173  }
  3174  
  3175  func TestClusterQueueReadiness(t *testing.T) {
  3176  	baseFalvor := utiltesting.MakeResourceFlavor("flavor1").Obj()
  3177  	baseCheck := utiltesting.MakeAdmissionCheck("check1").Active(metav1.ConditionTrue).Obj()
  3178  	baseQueue := utiltesting.MakeClusterQueue("queue1").
  3179  		ResourceGroup(
  3180  			*utiltesting.MakeFlavorQuotas(baseFalvor.Name).
  3181  				Resource(corev1.ResourceCPU, "10", "10").Obj()).
  3182  		AdmissionChecks(baseCheck.Name).
  3183  		Obj()
  3184  
  3185  	cases := map[string]struct {
  3186  		clusterQueues    []*kueue.ClusterQueue
  3187  		resourceFlavors  []*kueue.ResourceFlavor
  3188  		admissionChecks  []*kueue.AdmissionCheck
  3189  		clusterQueueName string
  3190  		terminate        bool
  3191  		wantStatus       metav1.ConditionStatus
  3192  		wantReason       string
  3193  		wantMessage      string
  3194  		wantActive       bool
  3195  		wantTerminating  bool
  3196  	}{
  3197  		"queue not found": {
  3198  			clusterQueueName: "queue1",
  3199  			wantStatus:       metav1.ConditionFalse,
  3200  			wantReason:       "NotFound",
  3201  			wantMessage:      "ClusterQueue not found",
  3202  		},
  3203  		"flavor not found": {
  3204  			clusterQueues:    []*kueue.ClusterQueue{baseQueue},
  3205  			admissionChecks:  []*kueue.AdmissionCheck{baseCheck},
  3206  			clusterQueueName: "queue1",
  3207  			wantStatus:       metav1.ConditionFalse,
  3208  			wantReason:       "FlavorNotFound",
  3209  			wantMessage:      "Can't admit new workloads: FlavorNotFound",
  3210  		},
  3211  		"check not found": {
  3212  			clusterQueues:    []*kueue.ClusterQueue{baseQueue},
  3213  			resourceFlavors:  []*kueue.ResourceFlavor{baseFalvor},
  3214  			clusterQueueName: "queue1",
  3215  			wantStatus:       metav1.ConditionFalse,
  3216  			wantReason:       "CheckNotFoundOrInactive",
  3217  			wantMessage:      "Can't admit new workloads: CheckNotFoundOrInactive",
  3218  		},
  3219  		"check inactive": {
  3220  			clusterQueues:    []*kueue.ClusterQueue{baseQueue},
  3221  			resourceFlavors:  []*kueue.ResourceFlavor{baseFalvor},
  3222  			admissionChecks:  []*kueue.AdmissionCheck{utiltesting.MakeAdmissionCheck("check1").Obj()},
  3223  			clusterQueueName: "queue1",
  3224  			wantStatus:       metav1.ConditionFalse,
  3225  			wantReason:       "CheckNotFoundOrInactive",
  3226  			wantMessage:      "Can't admit new workloads: CheckNotFoundOrInactive",
  3227  		},
  3228  		"flavor and check not found": {
  3229  			clusterQueues:    []*kueue.ClusterQueue{baseQueue},
  3230  			clusterQueueName: "queue1",
  3231  			wantStatus:       metav1.ConditionFalse,
  3232  			wantReason:       "FlavorNotFound",
  3233  			wantMessage:      "Can't admit new workloads: FlavorNotFound, CheckNotFoundOrInactive",
  3234  		},
  3235  		"terminating": {
  3236  			clusterQueues:    []*kueue.ClusterQueue{baseQueue},
  3237  			admissionChecks:  []*kueue.AdmissionCheck{baseCheck},
  3238  			resourceFlavors:  []*kueue.ResourceFlavor{baseFalvor},
  3239  			clusterQueueName: "queue1",
  3240  			terminate:        true,
  3241  			wantStatus:       metav1.ConditionFalse,
  3242  			wantReason:       "Terminating",
  3243  			wantMessage:      "Can't admit new workloads; clusterQueue is terminating",
  3244  			wantTerminating:  true,
  3245  		},
  3246  		"ready": {
  3247  			clusterQueues:    []*kueue.ClusterQueue{baseQueue},
  3248  			admissionChecks:  []*kueue.AdmissionCheck{baseCheck},
  3249  			resourceFlavors:  []*kueue.ResourceFlavor{baseFalvor},
  3250  			clusterQueueName: "queue1",
  3251  			wantStatus:       metav1.ConditionTrue,
  3252  			wantReason:       "Ready",
  3253  			wantMessage:      "Can admit new workloads",
  3254  			wantActive:       true,
  3255  		},
  3256  		"stopped": {
  3257  			clusterQueues:    []*kueue.ClusterQueue{utiltesting.MakeClusterQueue("queue1").StopPolicy(kueue.HoldAndDrain).Obj()},
  3258  			clusterQueueName: "queue1",
  3259  			wantStatus:       metav1.ConditionFalse,
  3260  			wantReason:       "Stopped",
  3261  			wantMessage:      "Can't admit new workloads: Stopped",
  3262  		},
  3263  	}
  3264  
  3265  	for name, tc := range cases {
  3266  		t.Run(name, func(t *testing.T) {
  3267  			cache := New(utiltesting.NewFakeClient())
  3268  			for _, rf := range tc.resourceFlavors {
  3269  				cache.AddOrUpdateResourceFlavor(rf)
  3270  			}
  3271  			for _, ac := range tc.admissionChecks {
  3272  				cache.AddOrUpdateAdmissionCheck(ac)
  3273  			}
  3274  			for _, cq := range tc.clusterQueues {
  3275  				if err := cache.AddClusterQueue(context.Background(), cq); err != nil {
  3276  					t.Errorf("failed to add clusterQueue %q: %v", cq.Name, err)
  3277  				}
  3278  			}
  3279  
  3280  			if tc.terminate {
  3281  				cache.TerminateClusterQueue(tc.clusterQueueName)
  3282  			}
  3283  
  3284  			gotStatus, gotReason, gotMessage := cache.ClusterQueueReadiness(tc.clusterQueueName)
  3285  
  3286  			if diff := cmp.Diff(tc.wantStatus, gotStatus); len(diff) != 0 {
  3287  				t.Errorf("Unexpected status (-want,+got):\n%s", diff)
  3288  			}
  3289  			if diff := cmp.Diff(tc.wantReason, gotReason); len(diff) != 0 {
  3290  				t.Errorf("Unexpected reason (-want,+got):\n%s", diff)
  3291  			}
  3292  			if diff := cmp.Diff(tc.wantMessage, gotMessage); len(diff) != 0 {
  3293  				t.Errorf("Unexpected message (-want,+got):\n%s", diff)
  3294  			}
  3295  
  3296  			if gotActive := cache.ClusterQueueActive(tc.clusterQueueName); gotActive != tc.wantActive {
  3297  				t.Errorf("Unexpected active state %v", gotActive)
  3298  			}
  3299  
  3300  			if gotTerminating := cache.ClusterQueueTerminating(tc.clusterQueueName); gotTerminating != tc.wantTerminating {
  3301  				t.Errorf("Unexpected terminating state %v", gotTerminating)
  3302  			}
  3303  		})
  3304  	}
  3305  }