sigs.k8s.io/kueue@v0.6.2/pkg/controller/jobframework/setup_test.go (about)

     1  /*
     2  Copyright 2024 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package jobframework
    18  
    19  import (
    20  	"context"
    21  	"net/http"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	"github.com/google/go-cmp/cmp/cmpopts"
    26  	kubeflow "github.com/kubeflow/mpi-operator/pkg/apis/kubeflow/v2beta1"
    27  	kftraining "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v1"
    28  	rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
    29  	rayjobapi "github.com/ray-project/kuberay/ray-operator/apis/ray/v1alpha1"
    30  	batchv1 "k8s.io/api/batch/v1"
    31  	corev1 "k8s.io/api/core/v1"
    32  	apimeta "k8s.io/apimachinery/pkg/api/meta"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/client-go/rest"
    36  	"k8s.io/klog/v2"
    37  	"sigs.k8s.io/controller-runtime/pkg/client"
    38  	ctrlmgr "sigs.k8s.io/controller-runtime/pkg/manager"
    39  	jobset "sigs.k8s.io/jobset/api/jobset/v1alpha2"
    40  
    41  	configapi "sigs.k8s.io/kueue/apis/config/v1beta1"
    42  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    43  	"sigs.k8s.io/kueue/pkg/util/slices"
    44  	utiltesting "sigs.k8s.io/kueue/pkg/util/testing"
    45  )
    46  
    47  func TestSetupControllers(t *testing.T) {
    48  	cases := map[string]struct {
    49  		opts       []Option
    50  		mapperGVKs []schema.GroupVersionKind
    51  		wantError  error
    52  	}{
    53  		"setup controllers succeed": {
    54  			opts: []Option{
    55  				WithEnabledFrameworks(&configapi.Integrations{
    56  					Frameworks: []string{"batch/job", "kubeflow.org/mpijob"},
    57  				}),
    58  			},
    59  			mapperGVKs: []schema.GroupVersionKind{
    60  				batchv1.SchemeGroupVersion.WithKind("Job"),
    61  				kubeflow.SchemeGroupVersionKind,
    62  			},
    63  		},
    64  		"mapper doesn't have kubeflow.org/mpijob, but no error occur": {
    65  			opts: []Option{
    66  				WithEnabledFrameworks(&configapi.Integrations{
    67  					Frameworks: []string{"batch/job", "kubeflow.org/mpijob"},
    68  				}),
    69  			},
    70  			mapperGVKs: []schema.GroupVersionKind{
    71  				batchv1.SchemeGroupVersion.WithKind("Job"),
    72  			},
    73  		},
    74  	}
    75  	for name, tc := range cases {
    76  		t.Run(name, func(t *testing.T) {
    77  			_, logger := utiltesting.ContextWithLog(t)
    78  			k8sClient := utiltesting.NewClientBuilder(jobset.AddToScheme, kubeflow.AddToScheme, rayjobapi.AddToScheme, kftraining.AddToScheme, rayv1.AddToScheme).Build()
    79  
    80  			mgrOpts := ctrlmgr.Options{
    81  				Scheme: k8sClient.Scheme(),
    82  				NewClient: func(*rest.Config, client.Options) (client.Client, error) {
    83  					return k8sClient, nil
    84  				},
    85  				MapperProvider: func(*rest.Config, *http.Client) (apimeta.RESTMapper, error) {
    86  					gvs := make([]schema.GroupVersion, len(tc.mapperGVKs))
    87  					for _, gvk := range tc.mapperGVKs {
    88  						gvs = append(gvs, gvk.GroupVersion())
    89  					}
    90  					mapper := apimeta.NewDefaultRESTMapper(gvs)
    91  					for _, gvk := range tc.mapperGVKs {
    92  						mapper.Add(gvk, apimeta.RESTScopeNamespace)
    93  					}
    94  					return mapper, nil
    95  				},
    96  			}
    97  			mgr, err := ctrlmgr.New(&rest.Config{}, mgrOpts)
    98  			if err != nil {
    99  				t.Fatalf("Failed to setup manager: %v", err)
   100  			}
   101  
   102  			gotError := SetupControllers(mgr, logger, tc.opts...)
   103  			if diff := cmp.Diff(tc.wantError, gotError, cmpopts.EquateErrors()); len(diff) != 0 {
   104  				t.Errorf("Unexpected error from SetupControllers (-want,+got):\n%s", diff)
   105  			}
   106  		})
   107  	}
   108  }
   109  
   110  func TestSetupIndexes(t *testing.T) {
   111  	testNamespace := "test"
   112  
   113  	cases := map[string]struct {
   114  		opts                  []Option
   115  		workloads             []kueue.Workload
   116  		filter                client.ListOption
   117  		wantError             error
   118  		wantFieldMatcherError bool
   119  		wantWorkloads         []string
   120  	}{
   121  		"proper indexes are set": {
   122  			workloads: []kueue.Workload{
   123  				*utiltesting.MakeWorkload("alpha-wl", testNamespace).
   124  					ControllerReference(batchv1.SchemeGroupVersion.WithKind("Job"), "alpha", "job").
   125  					Obj(),
   126  				*utiltesting.MakeWorkload("beta-wl", testNamespace).
   127  					ControllerReference(batchv1.SchemeGroupVersion.WithKind("Job"), "beta", "job").
   128  					Obj(),
   129  			},
   130  			opts: []Option{
   131  				WithEnabledFrameworks(&configapi.Integrations{
   132  					Frameworks: []string{"batch/job"},
   133  				}),
   134  			},
   135  			filter:        client.MatchingFields{GetOwnerKey(batchv1.SchemeGroupVersion.WithKind("Job")): "alpha"},
   136  			wantWorkloads: []string{"alpha-wl"},
   137  		},
   138  		"kubeflow.org/mpijob is disabled in the configAPI": {
   139  			workloads: []kueue.Workload{
   140  				*utiltesting.MakeWorkload("alpha-wl", testNamespace).
   141  					ControllerReference(kubeflow.SchemeGroupVersionKind, "alpha", "mpijob").
   142  					Obj(),
   143  				*utiltesting.MakeWorkload("beta-wl", testNamespace).
   144  					ControllerReference(kubeflow.SchemeGroupVersionKind, "beta", "mpijob").
   145  					Obj(),
   146  			},
   147  			opts: []Option{
   148  				WithEnabledFrameworks(&configapi.Integrations{
   149  					Frameworks: []string{"batch/job"},
   150  				}),
   151  			},
   152  			filter:                client.MatchingFields{GetOwnerKey(kubeflow.SchemeGroupVersionKind): "alpha"},
   153  			wantFieldMatcherError: true,
   154  		},
   155  	}
   156  	for name, tc := range cases {
   157  		t.Run(name, func(t *testing.T) {
   158  			ctx, cancel := context.WithCancel(context.Background())
   159  			defer cancel()
   160  
   161  			builder := utiltesting.NewClientBuilder().WithObjects(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}})
   162  			gotIndexerErr := SetupIndexes(ctx, utiltesting.AsIndexer(builder), tc.opts...)
   163  			if diff := cmp.Diff(tc.wantError, gotIndexerErr, cmpopts.EquateErrors()); len(diff) != 0 {
   164  				t.Fatalf("Unexpected setupIndexer error (-want,+got):\n%s", diff)
   165  			}
   166  			k8sClient := builder.Build()
   167  			for _, wl := range tc.workloads {
   168  				if err := k8sClient.Create(ctx, &wl); err != nil {
   169  					t.Fatalf("Unable to create workload, %q: %v", klog.KObj(&wl), err)
   170  				}
   171  			}
   172  
   173  			// In any case, a list operation without fieldMatcher should succeed.
   174  			gotWls := &kueue.WorkloadList{}
   175  			if gotListErr := k8sClient.List(ctx, gotWls, client.InNamespace(testNamespace)); gotListErr != nil {
   176  				t.Fatalf("Failed to list workloads without a fieldMatcher: %v", gotListErr)
   177  			}
   178  			deployedWlNames := slices.Map(tc.workloads, func(j *kueue.Workload) string { return j.Name })
   179  			gotWlNames := slices.Map(gotWls.Items, func(j *kueue.Workload) string { return j.Name })
   180  			if diff := cmp.Diff(deployedWlNames, gotWlNames, cmpopts.EquateEmpty(),
   181  				cmpopts.SortSlices(func(a, b string) bool { return a < b })); len(diff) != 0 {
   182  				t.Errorf("Unexpected list workloads (-want,+got):\n%s", diff)
   183  			}
   184  
   185  			// List workloads with fieldMatcher.
   186  			gotListErr := k8sClient.List(ctx, gotWls, client.InNamespace(testNamespace), tc.filter)
   187  			if (gotListErr != nil) != tc.wantFieldMatcherError {
   188  				t.Errorf("Unexpected list error\nwant: %v\ngot: %v", tc.wantFieldMatcherError, gotListErr)
   189  			}
   190  
   191  			if !tc.wantFieldMatcherError {
   192  				gotWlNames = slices.Map(gotWls.Items, func(j *kueue.Workload) string { return j.Name })
   193  				if diff := cmp.Diff(tc.wantWorkloads, gotWlNames, cmpopts.EquateEmpty(),
   194  					cmpopts.SortSlices(func(a, b string) bool { return a < b })); len(diff) != 0 {
   195  					t.Errorf("Unexpected list workloads (-want,+got):\n%s", diff)
   196  				}
   197  			}
   198  		})
   199  	}
   200  }