volcano.sh/volcano@v1.9.0/pkg/scheduler/uthelper/helper.go (about)

     1  /*
     2  Copyright 2024 The Volcano 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 uthelper
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	schedulingv1 "k8s.io/api/scheduling/v1"
    26  
    27  	"volcano.sh/apis/pkg/apis/scheduling"
    28  	vcapisv1 "volcano.sh/apis/pkg/apis/scheduling/v1beta1"
    29  	"volcano.sh/volcano/pkg/scheduler/api"
    30  	"volcano.sh/volcano/pkg/scheduler/cache"
    31  	"volcano.sh/volcano/pkg/scheduler/conf"
    32  	"volcano.sh/volcano/pkg/scheduler/framework"
    33  	"volcano.sh/volcano/pkg/scheduler/util"
    34  )
    35  
    36  // RegistPlugins plugins
    37  func RegistPlugins(plugins map[string]framework.PluginBuilder) {
    38  	for name, plugin := range plugins {
    39  		framework.RegisterPluginBuilder(name, plugin)
    40  	}
    41  }
    42  
    43  // TestCommonStruct is the most common used resource when do UT
    44  // others can wrap it in a new struct
    45  type TestCommonStruct struct {
    46  	Name      string
    47  	Plugins   map[string]framework.PluginBuilder // plugins for each case
    48  	Pods      []*v1.Pod
    49  	Nodes     []*v1.Node
    50  	PodGroups []*vcapisv1.PodGroup
    51  	Queues    []*vcapisv1.Queue
    52  	PriClass  []*schedulingv1.PriorityClass
    53  	Bind      map[string]string                      // bind results: ns/podName -> nodeName
    54  	PipeLined map[string][]string                    // pipelined results: map[jobID][]{nodename}
    55  	Evicted   []string                               // evicted pods list of ns/podName
    56  	Status    map[api.JobID]scheduling.PodGroupPhase // final status
    57  	BindsNum  int                                    // binds events numbers
    58  	EvictNum  int                                    // evict events numbers, include preempted and reclaimed evict events
    59  
    60  	// fake interface instance when check results need
    61  	stop       chan struct{}
    62  	binder     cache.Binder
    63  	evictor    cache.Evictor
    64  	stsUpdator cache.StatusUpdater
    65  	volBinder  cache.VolumeBinder
    66  	ssn        *framework.Session // store opened session
    67  }
    68  
    69  var _ Interface = &TestCommonStruct{}
    70  
    71  // RegistSession open session with tiers and configuration, and mock schedulerCache with self-defined FakeBinder and FakeEvictor
    72  func (test *TestCommonStruct) RegistSession(tiers []conf.Tier, config []conf.Configuration) *framework.Session {
    73  	binder := &util.FakeBinder{
    74  		Binds:   map[string]string{},
    75  		Channel: make(chan string),
    76  	}
    77  	evictor := &util.FakeEvictor{
    78  		Channel: make(chan string),
    79  	}
    80  	stsUpdator := &util.FakeStatusUpdater{}
    81  	test.binder = binder
    82  	test.evictor = evictor
    83  	test.stop = make(chan struct{})
    84  	// Create scheduler cache with self-defined binder and evictor
    85  	schedulerCache := cache.NewCustomMockSchedulerCache("utmock-scheduler", binder, evictor, stsUpdator, nil, nil, nil)
    86  	test.stsUpdator = schedulerCache.StatusUpdater
    87  	test.volBinder = schedulerCache.VolumeBinder
    88  
    89  	for _, node := range test.Nodes {
    90  		schedulerCache.AddOrUpdateNode(node)
    91  	}
    92  	for _, pod := range test.Pods {
    93  		schedulerCache.AddPod(pod)
    94  	}
    95  	for _, pg := range test.PodGroups {
    96  		schedulerCache.AddPodGroupV1beta1(pg)
    97  	}
    98  	for _, queue := range test.Queues {
    99  		schedulerCache.AddQueueV1beta1(queue)
   100  	}
   101  	for _, pc := range test.PriClass {
   102  		schedulerCache.AddPriorityClass(pc)
   103  	}
   104  
   105  	RegistPlugins(test.Plugins)
   106  	ssn := framework.OpenSession(schedulerCache, tiers, config)
   107  	test.ssn = ssn
   108  	schedulerCache.Run(test.stop)
   109  	return ssn
   110  }
   111  
   112  // Run choose to run passed in actions; if no actions provided, will panic
   113  func (test *TestCommonStruct) Run(actions []framework.Action) {
   114  	if len(actions) == 0 {
   115  		panic("no actions provided, please specify a list of actions to execute")
   116  	}
   117  	for _, action := range actions {
   118  		action.Initialize()
   119  		action.Execute(test.ssn)
   120  		action.UnInitialize()
   121  	}
   122  }
   123  
   124  // Close do release resource and clean up
   125  func (test *TestCommonStruct) Close() {
   126  	framework.CloseSession(test.ssn)
   127  	framework.CleanupPluginBuilders()
   128  	close(test.stop)
   129  }
   130  
   131  // CheckAll checks all the need status
   132  func (test *TestCommonStruct) CheckAll(caseIndex int) (err error) {
   133  	if err = test.CheckBind(caseIndex); err != nil {
   134  		return
   135  	}
   136  	if err = test.CheckEvict(caseIndex); err != nil {
   137  		return
   138  	}
   139  	if err = test.CheckPipelined(caseIndex); err != nil {
   140  		return
   141  	}
   142  	return test.CheckPGStatus(caseIndex)
   143  }
   144  
   145  // CheckBind check expected bind result
   146  func (test *TestCommonStruct) CheckBind(caseIndex int) error {
   147  	binder := test.binder.(*util.FakeBinder)
   148  	for i := 0; i < test.BindsNum; i++ {
   149  		select {
   150  		case <-binder.Channel:
   151  		case <-time.After(300 * time.Millisecond):
   152  			return fmt.Errorf("Failed to get Bind request in case %d(%s).", caseIndex, test.Name)
   153  		}
   154  	}
   155  
   156  	if len(test.Bind) != len(binder.Binds) {
   157  		return fmt.Errorf("case %d(%s) check bind: \nwant: %v, \ngot %v ", caseIndex, test.Name, test.Bind, binder.Binds)
   158  	}
   159  	for key, value := range test.Bind {
   160  		got := binder.Binds[key]
   161  		if value != got {
   162  			return fmt.Errorf("case %d(%s)  check bind: \nwant: %v->%v\n got: %v->%v ", caseIndex, test.Name, key, value, key, got)
   163  		}
   164  	}
   165  	return nil
   166  }
   167  
   168  // CheckEvict check the evicted result
   169  func (test *TestCommonStruct) CheckEvict(caseIndex int) error {
   170  	evictor := test.evictor.(*util.FakeEvictor)
   171  	for i := 0; i < test.EvictNum; i++ {
   172  		select {
   173  		case <-evictor.Channel:
   174  		case <-time.After(300 * time.Millisecond):
   175  			return fmt.Errorf("Failed to get Evict request in case %d(%s).", caseIndex, test.Name)
   176  		}
   177  	}
   178  
   179  	evicts := evictor.Evicts()
   180  	if len(test.Evicted) != len(evicts) {
   181  		return fmt.Errorf("case %d(%s) check evict: \nwant: %v, \ngot %v ", caseIndex, test.Name, test.Evicted, evicts)
   182  	}
   183  
   184  	expect := map[string]int{} // evicted number
   185  	got := map[string]int{}
   186  	for _, v := range test.Evicted {
   187  		expect[v]++
   188  	}
   189  	for _, v := range evicts {
   190  		got[v]++
   191  	}
   192  
   193  	if !reflect.DeepEqual(expect, got) {
   194  		return fmt.Errorf("case %d(%s) check evict: \nwant: %v\n got: %v ", caseIndex, test.Name, expect, got)
   195  	}
   196  	return nil
   197  }
   198  
   199  // CheckPGStatus check job's podgroups status
   200  func (test *TestCommonStruct) CheckPGStatus(caseIndex int) error {
   201  	ssn := test.ssn
   202  	for jobID, phase := range test.Status {
   203  		job := ssn.Jobs[jobID]
   204  		if job == nil {
   205  			return fmt.Errorf("case %d(%s) check podgroup status, job <%v> doesn't exist in session", caseIndex, test.Name, jobID)
   206  		}
   207  		got := job.PodGroup.Status.Phase
   208  		if phase != got {
   209  			return fmt.Errorf("case %d(%s) check podgroup <%v> status:\n want: %v, got: %v", caseIndex, test.Name, jobID, phase, got)
   210  		}
   211  	}
   212  	return nil
   213  }
   214  
   215  // CheckPipelined checks pipeline results
   216  func (test *TestCommonStruct) CheckPipelined(caseIndex int) error {
   217  	ssn := test.ssn
   218  	for jobID, nodes := range test.PipeLined {
   219  		job := ssn.Jobs[api.JobID(jobID)]
   220  		if job == nil {
   221  			return fmt.Errorf("case %d(%s) check pipeline, job <%v> doesn't exist in session", caseIndex, test.Name, jobID)
   222  		}
   223  		pipeLined := job.TaskStatusIndex[api.Pipelined]
   224  		if len(pipeLined) == 0 {
   225  			return fmt.Errorf("case %d(%s) check pipeline, want pipelined job: %v, actualy, no tasks pipelined to nodes %v", caseIndex, test.Name, jobID, nodes)
   226  		}
   227  		for _, task := range pipeLined {
   228  			if !Contains(nodes, task.NodeName) {
   229  				return fmt.Errorf("case %d(%s) check pipeline: actual: %v->%v, want: %v->%v", caseIndex, test.Name, task.Name, task.NodeName, task.Name, nodes)
   230  			}
   231  		}
   232  	}
   233  	return nil
   234  }