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  }