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  }