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 }