volcano.sh/volcano@v1.9.0/pkg/scheduler/util/test_utils.go (about) 1 /* 2 Copyright 2019 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 util 18 19 import ( 20 "context" 21 "fmt" 22 "sync" 23 "time" 24 25 v1 "k8s.io/api/core/v1" 26 schedulingv1 "k8s.io/api/scheduling/v1" 27 storagev1 "k8s.io/api/storage/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/client-go/informers" 31 "k8s.io/client-go/kubernetes" 32 "k8s.io/client-go/tools/cache" 33 "k8s.io/klog/v2" 34 35 schedulingv1beta1 "volcano.sh/apis/pkg/apis/scheduling/v1beta1" 36 "volcano.sh/volcano/pkg/scheduler/api" 37 volumescheduling "volcano.sh/volcano/pkg/scheduler/capabilities/volumebinding" 38 ) 39 40 // BuildNode builts node object 41 func BuildNode(name string, alloc v1.ResourceList, labels map[string]string) *v1.Node { 42 return &v1.Node{ 43 ObjectMeta: metav1.ObjectMeta{ 44 Name: name, 45 Labels: labels, 46 Annotations: map[string]string{}, 47 }, 48 Status: v1.NodeStatus{ 49 Capacity: alloc, 50 Allocatable: alloc, 51 }, 52 } 53 } 54 55 // BuildPod builds a Burstable pod object 56 func BuildPod(namespace, name, nodeName string, p v1.PodPhase, req v1.ResourceList, groupName string, labels map[string]string, selector map[string]string) *v1.Pod { 57 return &v1.Pod{ 58 ObjectMeta: metav1.ObjectMeta{ 59 UID: types.UID(fmt.Sprintf("%v-%v", namespace, name)), 60 Name: name, 61 Namespace: namespace, 62 Labels: labels, 63 Annotations: map[string]string{ 64 schedulingv1beta1.KubeGroupNameAnnotationKey: groupName, 65 }, 66 }, 67 Status: v1.PodStatus{ 68 Phase: p, 69 }, 70 Spec: v1.PodSpec{ 71 NodeName: nodeName, 72 NodeSelector: selector, 73 Containers: []v1.Container{ 74 { 75 Resources: v1.ResourceRequirements{ 76 Requests: req, 77 }, 78 }, 79 }, 80 }, 81 } 82 } 83 84 // BuildPodWithPVC builts Pod object with pvc volume 85 func BuildPodWithPVC(namespace, name, nodename string, p v1.PodPhase, req v1.ResourceList, pvc *v1.PersistentVolumeClaim, groupName string, labels map[string]string, selector map[string]string) *v1.Pod { 86 return &v1.Pod{ 87 ObjectMeta: metav1.ObjectMeta{ 88 UID: types.UID(fmt.Sprintf("%v-%v", namespace, name)), 89 Name: name, 90 Namespace: namespace, 91 Labels: labels, 92 Annotations: map[string]string{ 93 schedulingv1beta1.KubeGroupNameAnnotationKey: groupName, 94 }, 95 }, 96 Status: v1.PodStatus{ 97 Phase: p, 98 }, 99 Spec: v1.PodSpec{ 100 NodeName: nodename, 101 NodeSelector: selector, 102 Containers: []v1.Container{ 103 { 104 Resources: v1.ResourceRequirements{ 105 Requests: req, 106 }, 107 VolumeMounts: []v1.VolumeMount{ 108 { 109 Name: pvc.Name, 110 MountPath: "/data", 111 }, 112 }, 113 }, 114 }, 115 Volumes: []v1.Volume{ 116 { 117 Name: pvc.Name, 118 VolumeSource: v1.VolumeSource{ 119 PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ 120 ClaimName: pvc.Name, 121 }, 122 }, 123 }, 124 }, 125 }, 126 } 127 } 128 129 // BuildDynamicPVC create pv pvc and storage class 130 func BuildDynamicPVC(namespace, name string, req v1.ResourceList) (*v1.PersistentVolumeClaim, *v1.PersistentVolume, *storagev1.StorageClass) { 131 tmp := v1.PersistentVolumeReclaimDelete 132 tmp2 := storagev1.VolumeBindingWaitForFirstConsumer 133 sc := &storagev1.StorageClass{ 134 ObjectMeta: metav1.ObjectMeta{ 135 UID: types.UID(fmt.Sprintf("%v-%v", namespace, name)), 136 ResourceVersion: "1", 137 Name: name, 138 }, 139 Provisioner: name, 140 ReclaimPolicy: &tmp, 141 VolumeBindingMode: &tmp2, 142 } 143 tmp3 := v1.PersistentVolumeFilesystem 144 pvc := &v1.PersistentVolumeClaim{ 145 ObjectMeta: metav1.ObjectMeta{ 146 UID: types.UID(fmt.Sprintf("%v-%v", namespace, name)), 147 ResourceVersion: "1", 148 Namespace: namespace, 149 Name: name, 150 }, 151 Spec: v1.PersistentVolumeClaimSpec{ 152 Resources: v1.VolumeResourceRequirements{ 153 Requests: req, 154 }, 155 StorageClassName: &sc.Name, 156 VolumeMode: &tmp3, 157 }, 158 } 159 pv := &v1.PersistentVolume{ 160 ObjectMeta: metav1.ObjectMeta{ 161 UID: types.UID(fmt.Sprintf("%v-%v", namespace, name)), 162 ResourceVersion: "1", 163 Name: name, 164 }, 165 Spec: v1.PersistentVolumeSpec{ 166 StorageClassName: sc.Name, 167 Capacity: req, 168 VolumeMode: &tmp3, 169 AccessModes: []v1.PersistentVolumeAccessMode{ 170 v1.ReadWriteOnce, 171 }, 172 }, 173 Status: v1.PersistentVolumeStatus{ 174 Phase: v1.VolumeAvailable, 175 }, 176 } 177 return pvc, pv, sc 178 } 179 180 // BuildBestEffortPod builds a BestEffort pod object 181 func BuildBestEffortPod(namespace, name, nodeName string, p v1.PodPhase, groupName string, labels map[string]string, selector map[string]string) *v1.Pod { 182 return BuildPod(namespace, name, nodeName, p, v1.ResourceList{}, groupName, labels, selector) 183 } 184 185 // BuildPodWithPriority builds a pod object with priority 186 func BuildPodWithPriority(namespace, name, nodeName string, p v1.PodPhase, req v1.ResourceList, groupName string, labels map[string]string, selector map[string]string, priority *int32) *v1.Pod { 187 pod := BuildPod(namespace, name, nodeName, p, req, groupName, labels, selector) 188 pod.Spec.Priority = priority 189 return pod 190 } 191 192 // BuildPodGroup return podgroup with base spec and phase status 193 func BuildPodGroup(name, ns, queue string, minMember int32, taskMinMember map[string]int32, status schedulingv1beta1.PodGroupPhase) *schedulingv1beta1.PodGroup { 194 return &schedulingv1beta1.PodGroup{ 195 ObjectMeta: metav1.ObjectMeta{ 196 Name: name, 197 Namespace: ns, 198 }, 199 Spec: schedulingv1beta1.PodGroupSpec{ 200 Queue: queue, 201 MinMember: minMember, 202 MinTaskMember: taskMinMember, 203 }, 204 Status: schedulingv1beta1.PodGroupStatus{ 205 Phase: status, 206 }, 207 } 208 } 209 210 // BuildPodGroup return podgroup 211 func BuildPodGroupWithPrio(name, ns, queue string, minMember int32, taskMinMember map[string]int32, status schedulingv1beta1.PodGroupPhase, prioName string) *schedulingv1beta1.PodGroup { 212 pg := BuildPodGroup(name, ns, queue, minMember, taskMinMember, status) 213 pg.Spec.PriorityClassName = prioName 214 return pg 215 } 216 217 ///////////// function to build queue /////////////////// 218 219 // BuildQueue return a scheduling Queue 220 func BuildQueue(qname string, weight int32, cap v1.ResourceList) *schedulingv1beta1.Queue { 221 return &schedulingv1beta1.Queue{ 222 ObjectMeta: metav1.ObjectMeta{ 223 Name: qname, 224 }, 225 Spec: schedulingv1beta1.QueueSpec{ 226 Weight: weight, 227 Capability: cap, 228 }, 229 } 230 } 231 232 // BuildQueueWithAnnos return a Queue with annotations 233 func BuildQueueWithAnnos(qname string, weight int32, cap v1.ResourceList, annos map[string]string) *schedulingv1beta1.Queue { 234 queue := BuildQueue(qname, weight, cap) 235 queue.ObjectMeta.Annotations = annos 236 return queue 237 } 238 239 // BuildQueueWithResourcesQuantity return a queue with deserved and capability resources quantity. 240 func BuildQueueWithResourcesQuantity(qname string, deserved, cap v1.ResourceList) *schedulingv1beta1.Queue { 241 queue := BuildQueue(qname, 1, cap) 242 queue.Spec.Deserved = deserved 243 return queue 244 } 245 246 // ////// build in resource ////// 247 // BuildPriorityClass return pc 248 func BuildPriorityClass(name string, value int32) *schedulingv1.PriorityClass { 249 return &schedulingv1.PriorityClass{ 250 ObjectMeta: metav1.ObjectMeta{ 251 Name: name, 252 }, 253 Value: value, 254 } 255 } 256 257 // FakeBinder is used as fake binder 258 type FakeBinder struct { 259 sync.Mutex 260 Binds map[string]string 261 Channel chan string 262 } 263 264 // Bind used by fake binder struct to bind pods 265 func (fb *FakeBinder) Bind(kubeClient kubernetes.Interface, tasks []*api.TaskInfo) ([]*api.TaskInfo, error) { 266 fb.Lock() 267 defer fb.Unlock() 268 for _, p := range tasks { 269 key := fmt.Sprintf("%v/%v", p.Namespace, p.Name) 270 fb.Binds[key] = p.NodeName 271 fb.Channel <- key // need to wait binding pod because Bind process is asynchronous 272 } 273 274 return nil, nil 275 } 276 277 // FakeEvictor is used as fake evictor 278 type FakeEvictor struct { 279 sync.Mutex 280 evicts []string 281 Channel chan string 282 } 283 284 // Evicts returns copy of evicted pods. 285 func (fe *FakeEvictor) Evicts() []string { 286 fe.Lock() 287 defer fe.Unlock() 288 return append([]string{}, fe.evicts...) 289 } 290 291 // Evict is used by fake evictor to evict pods 292 func (fe *FakeEvictor) Evict(p *v1.Pod, reason string) error { 293 fe.Lock() 294 defer fe.Unlock() 295 296 fmt.Println("PodName: ", p.Name) 297 key := fmt.Sprintf("%v/%v", p.Namespace, p.Name) 298 fe.evicts = append(fe.evicts, key) 299 300 fe.Channel <- key 301 302 return nil 303 } 304 305 // FakeStatusUpdater is used for fake status update 306 type FakeStatusUpdater struct { 307 } 308 309 // UpdatePodCondition is a empty function 310 func (ftsu *FakeStatusUpdater) UpdatePodCondition(pod *v1.Pod, podCondition *v1.PodCondition) (*v1.Pod, error) { 311 // do nothing here 312 return nil, nil 313 } 314 315 // UpdatePodGroup is a empty function 316 func (ftsu *FakeStatusUpdater) UpdatePodGroup(pg *api.PodGroup) (*api.PodGroup, error) { 317 // do nothing here 318 return nil, nil 319 } 320 321 // UpdateQueueStatus do fake empty update 322 func (ftsu *FakeStatusUpdater) UpdateQueueStatus(queue *api.QueueInfo) error { 323 return nil 324 } 325 326 // FakeVolumeBinder is used as fake volume binder 327 type FakeVolumeBinder struct { 328 volumeBinder volumescheduling.SchedulerVolumeBinder 329 Actions map[string][]string 330 } 331 332 // NewFakeVolumeBinder create fake volume binder with kubeclient 333 func NewFakeVolumeBinder(kubeClient kubernetes.Interface) *FakeVolumeBinder { 334 logger := klog.FromContext(context.TODO()) 335 informerFactory := informers.NewSharedInformerFactory(kubeClient, 0) 336 podInformer := informerFactory.Core().V1().Pods() 337 pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims() 338 pvInformer := informerFactory.Core().V1().PersistentVolumes() 339 scInformer := informerFactory.Storage().V1().StorageClasses() 340 nodeInformer := informerFactory.Core().V1().Nodes() 341 csiNodeInformer := informerFactory.Storage().V1().CSINodes() 342 csiDriverInformer := informerFactory.Storage().V1().CSIDrivers() 343 csiStorageCapacityInformer := informerFactory.Storage().V1beta1().CSIStorageCapacities() 344 345 go podInformer.Informer().Run(context.TODO().Done()) 346 go pvcInformer.Informer().Run(context.TODO().Done()) 347 go pvInformer.Informer().Run(context.TODO().Done()) 348 go scInformer.Informer().Run(context.TODO().Done()) 349 go nodeInformer.Informer().Run(context.TODO().Done()) 350 go csiNodeInformer.Informer().Run(context.TODO().Done()) 351 go csiDriverInformer.Informer().Run(context.TODO().Done()) 352 go csiStorageCapacityInformer.Informer().Run(context.TODO().Done()) 353 354 cache.WaitForCacheSync(context.TODO().Done(), podInformer.Informer().HasSynced, 355 pvcInformer.Informer().HasSynced, 356 pvInformer.Informer().HasSynced, 357 scInformer.Informer().HasSynced, 358 nodeInformer.Informer().HasSynced, 359 csiNodeInformer.Informer().HasSynced, 360 csiDriverInformer.Informer().HasSynced, 361 csiStorageCapacityInformer.Informer().HasSynced) 362 363 capacityCheck := &volumescheduling.CapacityCheck{ 364 CSIDriverInformer: csiDriverInformer, 365 CSIStorageCapacityInformer: csiStorageCapacityInformer, 366 } 367 return &FakeVolumeBinder{ 368 volumeBinder: volumescheduling.NewVolumeBinder( 369 logger, 370 kubeClient, 371 podInformer, 372 nodeInformer, 373 csiNodeInformer, 374 pvcInformer, 375 pvInformer, 376 scInformer, 377 capacityCheck, 378 30*time.Second, 379 ), 380 Actions: make(map[string][]string), 381 } 382 } 383 384 // AllocateVolumes is a empty function 385 func (fvb *FakeVolumeBinder) AllocateVolumes(task *api.TaskInfo, hostname string, podVolumes *volumescheduling.PodVolumes) error { 386 if fvb.volumeBinder == nil { 387 return nil 388 } 389 logger := klog.FromContext(context.TODO()) 390 _, err := fvb.volumeBinder.AssumePodVolumes(logger, task.Pod, hostname, podVolumes) 391 392 key := fmt.Sprintf("%s/%s", task.Namespace, task.Name) 393 fvb.Actions[key] = append(fvb.Actions[key], "AllocateVolumes") 394 return err 395 } 396 397 // BindVolumes is a empty function 398 func (fvb *FakeVolumeBinder) BindVolumes(task *api.TaskInfo, podVolumes *volumescheduling.PodVolumes) error { 399 if fvb.volumeBinder == nil { 400 return nil 401 } 402 403 key := fmt.Sprintf("%s/%s", task.Namespace, task.Name) 404 if len(podVolumes.DynamicProvisions) > 0 { 405 fvb.Actions[key] = append(fvb.Actions[key], "DynamicProvisions") 406 } 407 if len(podVolumes.StaticBindings) > 0 { 408 fvb.Actions[key] = append(fvb.Actions[key], "StaticBindings") 409 } 410 return nil 411 } 412 413 // GetPodVolumes is a empty function 414 func (fvb *FakeVolumeBinder) GetPodVolumes(task *api.TaskInfo, node *v1.Node) (*volumescheduling.PodVolumes, error) { 415 if fvb.volumeBinder == nil { 416 return nil, nil 417 } 418 key := fmt.Sprintf("%s/%s", task.Namespace, task.Name) 419 fvb.Actions[key] = []string{"GetPodVolumes"} 420 logger := klog.FromContext(context.TODO()) 421 podVolumeClaims, err := fvb.volumeBinder.GetPodVolumeClaims(logger, task.Pod) 422 if err != nil { 423 return nil, err 424 } 425 // if len(unboundClaimsImmediate) > 0 { 426 // return nil, fmt.Errorf("pod has unbound immediate PersistentVolumeClaims") 427 // } 428 429 podVolumes, reasons, err := fvb.volumeBinder.FindPodVolumes(logger, task.Pod, podVolumeClaims, node) 430 if err != nil { 431 return nil, err 432 } else if len(reasons) > 0 { 433 return nil, fmt.Errorf("%v", reasons[0]) 434 } 435 return podVolumes, err 436 } 437 438 // RevertVolumes is a empty function 439 func (fvb *FakeVolumeBinder) RevertVolumes(task *api.TaskInfo, podVolumes *volumescheduling.PodVolumes) { 440 if fvb.volumeBinder == nil { 441 return 442 } 443 key := fmt.Sprintf("%s/%s", task.Namespace, task.Name) 444 fvb.Actions[key] = append(fvb.Actions[key], "RevertVolumes") 445 if podVolumes != nil { 446 fvb.volumeBinder.RevertAssumedPodVolumes(podVolumes) 447 } 448 }