sigs.k8s.io/kueue@v0.6.2/pkg/controller/admissionchecks/provisioning/indexer_test.go (about)

     1  /*
     2  Copyright 2023 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 provisioning
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	autoscaling "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1beta1"
    29  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    32  
    33  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    34  	"sigs.k8s.io/kueue/pkg/util/slices"
    35  	utiltesting "sigs.k8s.io/kueue/pkg/util/testing"
    36  )
    37  
    38  const (
    39  	TestNamespace = "ns"
    40  )
    41  
    42  func getClientBuilder() (*fake.ClientBuilder, context.Context) {
    43  	scheme := runtime.NewScheme()
    44  	if err := clientgoscheme.AddToScheme(scheme); err != nil {
    45  		panic(err)
    46  	}
    47  	if err := kueue.AddToScheme(scheme); err != nil {
    48  		panic(err)
    49  	}
    50  
    51  	if err := autoscaling.AddToScheme(scheme); err != nil {
    52  		panic(err)
    53  	}
    54  
    55  	ctx := context.Background()
    56  	builder := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Namespace{
    57  		ObjectMeta: metav1.ObjectMeta{
    58  			Name: TestNamespace,
    59  		},
    60  	})
    61  	_ = SetupIndexer(ctx, utiltesting.AsIndexer(builder))
    62  	return builder, ctx
    63  }
    64  
    65  func TestIndexProvisioningRequests(t *testing.T) {
    66  	cases := map[string]struct {
    67  		requests      []*autoscaling.ProvisioningRequest
    68  		filter        client.ListOption
    69  		wantListError error
    70  		wantList      []string
    71  	}{
    72  		"no owner": {
    73  			requests: []*autoscaling.ProvisioningRequest{
    74  				{
    75  					ObjectMeta: metav1.ObjectMeta{
    76  						Namespace: "default",
    77  						Name:      "name",
    78  					},
    79  				},
    80  			},
    81  			filter: client.MatchingFields{RequestsOwnedByWorkloadKey: "wl"},
    82  		},
    83  		"single owner, single match": {
    84  			requests: []*autoscaling.ProvisioningRequest{
    85  				{
    86  					ObjectMeta: metav1.ObjectMeta{
    87  						Namespace: "default",
    88  						Name:      "name",
    89  					},
    90  				},
    91  				{
    92  					ObjectMeta: metav1.ObjectMeta{
    93  						Namespace: "default",
    94  						Name:      "name2",
    95  						OwnerReferences: []metav1.OwnerReference{
    96  							{
    97  								Name: "wl",
    98  							},
    99  						},
   100  					},
   101  				},
   102  			},
   103  			filter:   client.MatchingFields{RequestsOwnedByWorkloadKey: "wl"},
   104  			wantList: []string{"name2"},
   105  		},
   106  		"multiple owners, multiple matches": {
   107  			requests: []*autoscaling.ProvisioningRequest{
   108  				{
   109  					ObjectMeta: metav1.ObjectMeta{
   110  						Namespace: "default",
   111  						Name:      "name",
   112  					},
   113  				},
   114  				{
   115  					ObjectMeta: metav1.ObjectMeta{
   116  						Namespace: "default",
   117  						Name:      "name2",
   118  						OwnerReferences: []metav1.OwnerReference{
   119  							{
   120  								Name: "wl",
   121  							},
   122  						},
   123  					},
   124  				},
   125  				{
   126  					ObjectMeta: metav1.ObjectMeta{
   127  						Namespace: "default",
   128  						Name:      "name3",
   129  						OwnerReferences: []metav1.OwnerReference{
   130  							{
   131  								Name: "wl_2",
   132  							},
   133  							{
   134  								Name: "wl",
   135  							},
   136  						},
   137  					},
   138  				},
   139  			},
   140  			filter:   client.MatchingFields{RequestsOwnedByWorkloadKey: "wl"},
   141  			wantList: []string{"name2", "name3"},
   142  		},
   143  	}
   144  	for name, tc := range cases {
   145  		t.Run(name, func(t *testing.T) {
   146  			builder, ctx := getClientBuilder()
   147  			k8sclient := builder.Build()
   148  			for _, req := range tc.requests {
   149  				if err := k8sclient.Create(ctx, req); err != nil {
   150  					t.Errorf("Unable to create %s request: %v", client.ObjectKeyFromObject(req), err)
   151  				}
   152  			}
   153  
   154  			lst := &autoscaling.ProvisioningRequestList{}
   155  
   156  			gotListErr := k8sclient.List(ctx, lst, client.InNamespace("default"), tc.filter)
   157  			if diff := cmp.Diff(tc.wantListError, gotListErr); diff != "" {
   158  				t.Errorf("unexpected list error (-want/+got):\n%s", diff)
   159  			}
   160  
   161  			gotList := slices.Map(lst.Items, func(pr *autoscaling.ProvisioningRequest) string { return pr.Name })
   162  			if diff := cmp.Diff(tc.wantList, gotList, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {
   163  				t.Errorf("unexpected list (-want/+got):\n%s", diff)
   164  			}
   165  		})
   166  	}
   167  }
   168  
   169  func TestIndexWorkload(t *testing.T) {
   170  	cases := map[string]struct {
   171  		workloads     []*kueue.Workload
   172  		filter        client.ListOption
   173  		wantListError error
   174  		wantList      []string
   175  	}{
   176  		"no checks": {
   177  			workloads: []*kueue.Workload{
   178  				{
   179  					ObjectMeta: metav1.ObjectMeta{
   180  						Namespace: "default",
   181  						Name:      "name",
   182  					},
   183  				},
   184  			},
   185  			filter: client.MatchingFields{WorkloadsWithAdmissionCheckKey: "check"},
   186  		},
   187  		"single check, single match": {
   188  			workloads: []*kueue.Workload{
   189  				{
   190  					ObjectMeta: metav1.ObjectMeta{
   191  						Namespace: "default",
   192  						Name:      "name",
   193  					},
   194  				},
   195  				{
   196  					ObjectMeta: metav1.ObjectMeta{
   197  						Namespace: "default",
   198  						Name:      "name2",
   199  					},
   200  					Status: kueue.WorkloadStatus{
   201  						AdmissionChecks: []kueue.AdmissionCheckState{
   202  							{
   203  								Name: "check",
   204  							},
   205  						},
   206  					},
   207  				},
   208  			},
   209  			filter:   client.MatchingFields{WorkloadsWithAdmissionCheckKey: "check"},
   210  			wantList: []string{"name2"},
   211  		},
   212  		"multiple checks, multiple matches": {
   213  			workloads: []*kueue.Workload{
   214  				{
   215  					ObjectMeta: metav1.ObjectMeta{
   216  						Namespace: "default",
   217  						Name:      "name",
   218  					},
   219  				},
   220  				{
   221  					ObjectMeta: metav1.ObjectMeta{
   222  						Namespace: "default",
   223  						Name:      "name2",
   224  					},
   225  					Status: kueue.WorkloadStatus{
   226  						AdmissionChecks: []kueue.AdmissionCheckState{
   227  							{
   228  								Name: "check",
   229  							},
   230  						},
   231  					},
   232  				},
   233  				{
   234  					ObjectMeta: metav1.ObjectMeta{
   235  						Namespace: "default",
   236  						Name:      "name3",
   237  					},
   238  					Status: kueue.WorkloadStatus{
   239  						AdmissionChecks: []kueue.AdmissionCheckState{
   240  							{
   241  								Name: "check2",
   242  							},
   243  							{
   244  								Name: "check",
   245  							},
   246  						},
   247  					},
   248  				},
   249  			},
   250  			filter:   client.MatchingFields{WorkloadsWithAdmissionCheckKey: "check"},
   251  			wantList: []string{"name2", "name3"},
   252  		},
   253  	}
   254  	for name, tc := range cases {
   255  		t.Run(name, func(t *testing.T) {
   256  			builder, ctx := getClientBuilder()
   257  			k8sclient := builder.Build()
   258  			for _, wl := range tc.workloads {
   259  				if err := k8sclient.Create(ctx, wl); err != nil {
   260  					t.Errorf("Unable to create %s workload: %v", client.ObjectKeyFromObject(wl), err)
   261  				}
   262  			}
   263  
   264  			lst := &kueue.WorkloadList{}
   265  
   266  			gotListErr := k8sclient.List(ctx, lst, client.InNamespace("default"), tc.filter)
   267  			if diff := cmp.Diff(tc.wantListError, gotListErr); diff != "" {
   268  				t.Errorf("unexpected list error (-want/+got):\n%s", diff)
   269  			}
   270  
   271  			gotList := slices.Map(lst.Items, func(wl *kueue.Workload) string { return wl.Name })
   272  			if diff := cmp.Diff(tc.wantList, gotList, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {
   273  				t.Errorf("unexpected list (-want/+got):\n%s", diff)
   274  			}
   275  		})
   276  	}
   277  }
   278  
   279  func TestIndexAdmissionChecks(t *testing.T) {
   280  	cases := map[string]struct {
   281  		checks   []*kueue.AdmissionCheck
   282  		filter   client.ListOption
   283  		wantList []string
   284  	}{
   285  		"different controller": {
   286  			checks: []*kueue.AdmissionCheck{
   287  				utiltesting.MakeAdmissionCheck("check1").
   288  					ControllerName("other").
   289  					Parameters(kueue.GroupVersion.Group, ConfigKind, "config1").
   290  					Obj(),
   291  			},
   292  			filter: client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"},
   293  		},
   294  		"bad ref group": {
   295  			checks: []*kueue.AdmissionCheck{
   296  				utiltesting.MakeAdmissionCheck("check1").
   297  					ControllerName(ControllerName).
   298  					Parameters("core", ConfigKind, "config1").
   299  					Obj(),
   300  			},
   301  			filter: client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"},
   302  		},
   303  		"bad ref kind": {
   304  			checks: []*kueue.AdmissionCheck{
   305  				utiltesting.MakeAdmissionCheck("check1").
   306  					ControllerName(ControllerName).
   307  					Parameters(kueue.GroupVersion.Group, "kind", "config1").
   308  					Obj(),
   309  			},
   310  			filter: client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"},
   311  		},
   312  		"empty name": {
   313  			checks: []*kueue.AdmissionCheck{
   314  				utiltesting.MakeAdmissionCheck("check1").
   315  					ControllerName(ControllerName).
   316  					Parameters(kueue.GroupVersion.Group, ConfigKind, "").
   317  					Obj(),
   318  			},
   319  			filter: client.MatchingFields{AdmissionCheckUsingConfigKey: ""},
   320  		},
   321  		"match": {
   322  			checks: []*kueue.AdmissionCheck{
   323  				utiltesting.MakeAdmissionCheck("check1").
   324  					ControllerName(ControllerName).
   325  					Parameters(kueue.GroupVersion.Group, ConfigKind, "config1").
   326  					Obj(),
   327  			},
   328  			filter:   client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"},
   329  			wantList: []string{"check1"},
   330  		},
   331  		"multiple checks, partial match": {
   332  			checks: []*kueue.AdmissionCheck{
   333  				utiltesting.MakeAdmissionCheck("check1").
   334  					ControllerName(ControllerName).
   335  					Parameters(kueue.GroupVersion.Group, ConfigKind, "config1").
   336  					Obj(),
   337  				utiltesting.MakeAdmissionCheck("check2").
   338  					ControllerName(ControllerName).
   339  					Parameters(kueue.GroupVersion.Group, ConfigKind, "config1").
   340  					Obj(),
   341  				utiltesting.MakeAdmissionCheck("check3").
   342  					ControllerName(ControllerName).
   343  					Parameters(kueue.GroupVersion.Group, ConfigKind, "config2").
   344  					Obj(),
   345  			},
   346  			filter:   client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"},
   347  			wantList: []string{"check1", "check2"},
   348  		},
   349  	}
   350  	for name, tc := range cases {
   351  		t.Run(name, func(t *testing.T) {
   352  			builder, ctx := getClientBuilder()
   353  			k8sclient := builder.Build()
   354  			for _, req := range tc.checks {
   355  				if err := k8sclient.Create(ctx, req); err != nil {
   356  					t.Errorf("Unable to create %s request: %v", client.ObjectKeyFromObject(req), err)
   357  				}
   358  			}
   359  
   360  			lst := &kueue.AdmissionCheckList{}
   361  
   362  			gotListErr := k8sclient.List(ctx, lst, tc.filter)
   363  			if gotListErr != nil {
   364  				t.Errorf("unexpected list error:%s", gotListErr)
   365  			}
   366  
   367  			gotList := slices.Map(lst.Items, func(ac *kueue.AdmissionCheck) string { return ac.Name })
   368  			if diff := cmp.Diff(tc.wantList, gotList, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" {
   369  				t.Errorf("unexpected list (-want/+got):\n%s", diff)
   370  			}
   371  		})
   372  	}
   373  }