volcano.sh/volcano@v1.9.0/pkg/scheduler/cache/cache_test.go (about) 1 /* 2 Copyright 2017 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 "reflect" 23 "testing" 24 "time" 25 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/types" 29 "k8s.io/client-go/tools/record" 30 "volcano.sh/volcano/pkg/scheduler/api" 31 "volcano.sh/volcano/pkg/scheduler/util" 32 ) 33 34 func buildNode(name string, alloc v1.ResourceList) *v1.Node { 35 return &v1.Node{ 36 ObjectMeta: metav1.ObjectMeta{ 37 UID: types.UID(name), 38 Name: name, 39 }, 40 Status: v1.NodeStatus{ 41 Capacity: alloc, 42 Allocatable: alloc, 43 }, 44 } 45 } 46 47 func buildPod(ns, n, nn string, 48 p v1.PodPhase, req v1.ResourceList, 49 owner []metav1.OwnerReference, labels map[string]string) *v1.Pod { 50 51 return &v1.Pod{ 52 ObjectMeta: metav1.ObjectMeta{ 53 UID: types.UID(fmt.Sprintf("%v-%v", ns, n)), 54 Name: n, 55 Namespace: ns, 56 OwnerReferences: owner, 57 Labels: labels, 58 }, 59 Status: v1.PodStatus{ 60 Phase: p, 61 }, 62 Spec: v1.PodSpec{ 63 NodeName: nn, 64 Containers: []v1.Container{ 65 { 66 Resources: v1.ResourceRequirements{ 67 Requests: req, 68 }, 69 }, 70 }, 71 }, 72 } 73 } 74 75 func buildOwnerReference(owner string) metav1.OwnerReference { 76 controller := true 77 return metav1.OwnerReference{ 78 Controller: &controller, 79 UID: types.UID(owner), 80 } 81 } 82 83 func TestGetOrCreateJob(t *testing.T) { 84 owner1 := buildOwnerReference("j1") 85 owner2 := buildOwnerReference("j2") 86 87 pod1 := buildPod("c1", "p1", "n1", v1.PodRunning, api.BuildResourceList("1000m", "1G"), 88 []metav1.OwnerReference{owner1}, make(map[string]string)) 89 pi1 := api.NewTaskInfo(pod1) 90 pi1.Job = "j1" // The job name is set by cache. 91 92 pod2 := buildPod("c1", "p2", "n1", v1.PodRunning, api.BuildResourceList("1000m", "1G"), 93 []metav1.OwnerReference{owner2}, make(map[string]string)) 94 pod2.Spec.SchedulerName = "volcano" 95 pi2 := api.NewTaskInfo(pod2) 96 97 pod3 := buildPod("c3", "p3", "n1", v1.PodRunning, api.BuildResourceList("1000m", "1G"), 98 []metav1.OwnerReference{owner2}, make(map[string]string)) 99 pi3 := api.NewTaskInfo(pod3) 100 101 cache := &SchedulerCache{ 102 Nodes: make(map[string]*api.NodeInfo), 103 Jobs: make(map[api.JobID]*api.JobInfo), 104 schedulerNames: []string{"volcano"}, 105 } 106 107 tests := []struct { 108 task *api.TaskInfo 109 gotJob bool // whether getOrCreateJob will return job for corresponding task 110 }{ 111 { 112 task: pi1, 113 gotJob: true, 114 }, 115 { 116 task: pi2, 117 gotJob: false, 118 }, 119 { 120 task: pi3, 121 gotJob: false, 122 }, 123 } 124 for i, test := range tests { 125 result := cache.getOrCreateJob(test.task) != nil 126 if result != test.gotJob { 127 t.Errorf("case %d: \n expected %t, \n got %t \n", 128 i, test.gotJob, result) 129 } 130 } 131 } 132 133 func TestSchedulerCache_Bind_NodeWithSufficientResources(t *testing.T) { 134 owner := buildOwnerReference("j1") 135 136 cache := &SchedulerCache{ 137 Jobs: make(map[api.JobID]*api.JobInfo), 138 Nodes: make(map[string]*api.NodeInfo), 139 Binder: &util.FakeBinder{ 140 Binds: map[string]string{}, 141 Channel: make(chan string), 142 }, 143 BindFlowChannel: make(chan *api.TaskInfo, 5000), 144 } 145 146 pod := buildPod("c1", "p1", "", v1.PodPending, api.BuildResourceList("1000m", "1G"), 147 []metav1.OwnerReference{owner}, make(map[string]string)) 148 cache.AddPod(pod) 149 150 node := buildNode("n1", api.BuildResourceList("2000m", "10G", []api.ScalarResource{{Name: "pods", Value: "10"}}...)) 151 cache.AddOrUpdateNode(node) 152 153 task := api.NewTaskInfo(pod) 154 task.Job = "j1" 155 if err := cache.addTask(task); err != nil { 156 t.Errorf("failed to add task %v", err) 157 } 158 task.NodeName = "n1" 159 err := cache.AddBindTask(task) 160 if err != nil { 161 t.Errorf("failed to bind pod to node: %v", err) 162 } 163 } 164 165 func TestSchedulerCache_Bind_NodeWithInsufficientResources(t *testing.T) { 166 owner := buildOwnerReference("j1") 167 168 cache := &SchedulerCache{ 169 Jobs: make(map[api.JobID]*api.JobInfo), 170 Nodes: make(map[string]*api.NodeInfo), 171 Binder: &util.FakeBinder{ 172 Binds: map[string]string{}, 173 Channel: make(chan string), 174 }, 175 BindFlowChannel: make(chan *api.TaskInfo, 5000), 176 } 177 178 pod := buildPod("c1", "p1", "", v1.PodPending, api.BuildResourceList("5000m", "50G"), 179 []metav1.OwnerReference{owner}, make(map[string]string)) 180 cache.AddPod(pod) 181 182 node := buildNode("n1", api.BuildResourceList("2000m", "10G", []api.ScalarResource{{Name: "pods", Value: "10"}}...)) 183 cache.AddOrUpdateNode(node) 184 185 task := api.NewTaskInfo(pod) 186 task.Job = "j1" 187 188 if err := cache.addTask(task); err != nil { 189 t.Errorf("failed to add task %v", err) 190 } 191 192 task.NodeName = "n1" 193 taskBeforeBind := task.Clone() 194 nodeBeforeBind := cache.Nodes["n1"].Clone() 195 196 err := cache.AddBindTask(task) 197 if err == nil { 198 t.Errorf("expected bind to fail for node with insufficient resources") 199 } 200 201 _, taskAfterBind, err := cache.findJobAndTask(task) 202 if err != nil { 203 t.Errorf("expected to find task after failed bind") 204 } 205 if !reflect.DeepEqual(taskBeforeBind, taskAfterBind) { 206 t.Errorf("expected task to remain the same after failed bind: \n %#v\n %#v", taskBeforeBind, taskAfterBind) 207 } 208 209 nodeAfterBind := cache.Nodes["n1"].Clone() 210 if !reflect.DeepEqual(nodeBeforeBind, nodeAfterBind) { 211 t.Errorf("expected node to remain the same after failed bind") 212 } 213 } 214 215 func TestNodeOperation(t *testing.T) { 216 // case 1 217 node1 := buildNode("n1", api.BuildResourceList("2000m", "10G")) 218 node2 := buildNode("n2", api.BuildResourceList("4000m", "16G")) 219 node3 := buildNode("n3", api.BuildResourceList("3000m", "12G")) 220 nodeInfo1 := api.NewNodeInfo(node1) 221 nodeInfo2 := api.NewNodeInfo(node2) 222 nodeInfo3 := api.NewNodeInfo(node3) 223 tests := []struct { 224 deletedNode *v1.Node 225 nodes []*v1.Node 226 expected *SchedulerCache 227 delExpect *SchedulerCache 228 }{ 229 { 230 deletedNode: node2, 231 nodes: []*v1.Node{node1, node2, node3}, 232 expected: &SchedulerCache{ 233 Nodes: map[string]*api.NodeInfo{ 234 "n1": nodeInfo1, 235 "n2": nodeInfo2, 236 "n3": nodeInfo3, 237 }, 238 NodeList: []string{"n1", "n2", "n3"}, 239 }, 240 delExpect: &SchedulerCache{ 241 Nodes: map[string]*api.NodeInfo{ 242 "n1": nodeInfo1, 243 "n3": nodeInfo3, 244 }, 245 NodeList: []string{"n1", "n3"}, 246 }, 247 }, 248 { 249 deletedNode: node1, 250 nodes: []*v1.Node{node1, node2, node3}, 251 expected: &SchedulerCache{ 252 Nodes: map[string]*api.NodeInfo{ 253 "n1": nodeInfo1, 254 "n2": nodeInfo2, 255 "n3": nodeInfo3, 256 }, 257 NodeList: []string{"n1", "n2", "n3"}, 258 }, 259 delExpect: &SchedulerCache{ 260 Nodes: map[string]*api.NodeInfo{ 261 "n2": nodeInfo2, 262 "n3": nodeInfo3, 263 }, 264 NodeList: []string{"n2", "n3"}, 265 }, 266 }, 267 { 268 deletedNode: node3, 269 nodes: []*v1.Node{node1, node2, node3}, 270 expected: &SchedulerCache{ 271 Nodes: map[string]*api.NodeInfo{ 272 "n1": nodeInfo1, 273 "n2": nodeInfo2, 274 "n3": nodeInfo3, 275 }, 276 NodeList: []string{"n1", "n2", "n3"}, 277 }, 278 delExpect: &SchedulerCache{ 279 Nodes: map[string]*api.NodeInfo{ 280 "n1": nodeInfo1, 281 "n2": nodeInfo2, 282 }, 283 NodeList: []string{"n1", "n2"}, 284 }, 285 }, 286 } 287 288 for i, test := range tests { 289 cache := &SchedulerCache{ 290 Nodes: make(map[string]*api.NodeInfo), 291 NodeList: []string{}, 292 } 293 294 for _, n := range test.nodes { 295 cache.AddOrUpdateNode(n) 296 } 297 298 if !reflect.DeepEqual(cache, test.expected) { 299 t.Errorf("case %d: \n expected %v, \n got %v \n", 300 i, test.expected, cache) 301 } 302 303 // delete node 304 cache.RemoveNode(test.deletedNode.Name) 305 if !reflect.DeepEqual(cache, test.delExpect) { 306 t.Errorf("case %d: \n expected %v, \n got %v \n", 307 i, test.delExpect, cache) 308 } 309 } 310 } 311 312 func TestBindTasks(t *testing.T) { 313 owner := buildOwnerReference("j1") 314 scheduler := "fake-scheduler" 315 316 ctx, cancel := context.WithCancel(context.Background()) 317 defer cancel() 318 319 sc := NewDefaultMockSchedulerCache(scheduler) 320 sc.Run(ctx.Done()) 321 kubeCli := sc.kubeClient 322 323 pod := buildPod("c1", "p1", "", v1.PodPending, api.BuildResourceList("1000m", "1G"), []metav1.OwnerReference{owner}, make(map[string]string)) 324 node := buildNode("n1", api.BuildResourceList("2000m", "10G", []api.ScalarResource{{Name: "pods", Value: "10"}}...)) 325 pod.Annotations = map[string]string{"scheduling.k8s.io/group-name": "j1"} 326 pod.Spec.SchedulerName = scheduler 327 328 // make sure pod exist when calling fake client binding 329 kubeCli.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) 330 // set node in cache directly 331 kubeCli.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}) 332 333 // wait for pod synced 334 time.Sleep(100 * time.Millisecond) 335 task := api.NewTaskInfo(pod) 336 task.NodeName = "n1" 337 err := sc.AddBindTask(task) 338 if err != nil { 339 t.Errorf("failed to bind pod to node: %v", err) 340 } 341 time.Sleep(100 * time.Millisecond) 342 r := sc.Recorder.(*record.FakeRecorder) 343 if len(r.Events) != 1 { 344 t.Fatalf("succesfully binding task should have 1 event") 345 } 346 }