volcano.sh/volcano@v1.9.0/pkg/controllers/job/job_controller_handler_test.go (about)

     1  /*
     2  Copyright 2019 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 job
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	"k8s.io/apimachinery/pkg/util/uuid"
    27  	"k8s.io/client-go/informers"
    28  	kubeclientset "k8s.io/client-go/kubernetes"
    29  	"k8s.io/client-go/rest"
    30  
    31  	batch "volcano.sh/apis/pkg/apis/batch/v1alpha1"
    32  	bus "volcano.sh/apis/pkg/apis/bus/v1alpha1"
    33  	"volcano.sh/apis/pkg/apis/helpers"
    34  	scheduling "volcano.sh/apis/pkg/apis/scheduling/v1beta1"
    35  	vcclientset "volcano.sh/apis/pkg/client/clientset/versioned"
    36  	"volcano.sh/volcano/pkg/controllers/framework"
    37  )
    38  
    39  func newController() *jobcontroller {
    40  	kubeClientSet := kubeclientset.NewForConfigOrDie(&rest.Config{
    41  		Host: "",
    42  		ContentConfig: rest.ContentConfig{
    43  			GroupVersion: &v1.SchemeGroupVersion,
    44  		},
    45  	},
    46  	)
    47  
    48  	vcclient := vcclientset.NewForConfigOrDie(&rest.Config{
    49  		Host: "",
    50  		ContentConfig: rest.ContentConfig{
    51  			GroupVersion: &batch.SchemeGroupVersion,
    52  		},
    53  	})
    54  
    55  	sharedInformers := informers.NewSharedInformerFactory(kubeClientSet, 0)
    56  
    57  	controller := &jobcontroller{}
    58  	opt := &framework.ControllerOption{
    59  		VolcanoClient:         vcclient,
    60  		KubeClient:            kubeClientSet,
    61  		SharedInformerFactory: sharedInformers,
    62  		WorkerNum:             3,
    63  	}
    64  
    65  	controller.Initialize(opt)
    66  
    67  	return controller
    68  }
    69  
    70  func buildPod(namespace, name string, p v1.PodPhase, labels map[string]string) *v1.Pod {
    71  	boolValue := true
    72  	return &v1.Pod{
    73  		ObjectMeta: metav1.ObjectMeta{
    74  			UID:             types.UID(fmt.Sprintf("%v-%v", namespace, name)),
    75  			Name:            name,
    76  			Namespace:       namespace,
    77  			Labels:          labels,
    78  			ResourceVersion: string(uuid.NewUUID()),
    79  			OwnerReferences: []metav1.OwnerReference{
    80  				{
    81  					APIVersion: helpers.JobKind.GroupVersion().String(),
    82  					Kind:       helpers.JobKind.Kind,
    83  					Controller: &boolValue,
    84  				},
    85  			},
    86  		},
    87  		Status: v1.PodStatus{
    88  			Phase: p,
    89  		},
    90  		Spec: v1.PodSpec{
    91  			Containers: []v1.Container{
    92  				{
    93  					Name:  "nginx",
    94  					Image: "nginx:latest",
    95  				},
    96  			},
    97  		},
    98  	}
    99  }
   100  
   101  func addPodAnnotation(pod *v1.Pod, annotations map[string]string) *v1.Pod {
   102  	podWithAnnotation := pod
   103  	for key, value := range annotations {
   104  		if podWithAnnotation.Annotations == nil {
   105  			podWithAnnotation.Annotations = make(map[string]string)
   106  		}
   107  		podWithAnnotation.Annotations[key] = value
   108  	}
   109  	return podWithAnnotation
   110  }
   111  
   112  func TestAddCommandFunc(t *testing.T) {
   113  
   114  	namespace := "test"
   115  
   116  	testCases := []struct {
   117  		Name        string
   118  		command     interface{}
   119  		ExpectValue int
   120  	}{
   121  		{
   122  			Name: "AddCommand Success Case",
   123  			command: &bus.Command{
   124  				ObjectMeta: metav1.ObjectMeta{
   125  					Name:      "Valid Command",
   126  					Namespace: namespace,
   127  				},
   128  			},
   129  			ExpectValue: 1,
   130  		},
   131  		{
   132  			Name:        "AddCommand Failure Case",
   133  			command:     "Command",
   134  			ExpectValue: 0,
   135  		},
   136  	}
   137  
   138  	for i, testcase := range testCases {
   139  		t.Run(testcase.Name, func(t *testing.T) {
   140  			controller := newController()
   141  			controller.addCommand(testcase.command)
   142  			len := controller.commandQueue.Len()
   143  			if testcase.ExpectValue != len {
   144  				t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, len)
   145  			}
   146  		})
   147  	}
   148  }
   149  
   150  func TestJobAddFunc(t *testing.T) {
   151  	namespace := "test"
   152  
   153  	testCases := []struct {
   154  		Name        string
   155  		job         *batch.Job
   156  		ExpectValue int
   157  	}{
   158  		{
   159  			Name: "AddJob Success",
   160  			job: &batch.Job{
   161  				ObjectMeta: metav1.ObjectMeta{
   162  					Name:      "Job1",
   163  					Namespace: namespace,
   164  				},
   165  			},
   166  			ExpectValue: 1,
   167  		},
   168  	}
   169  	for i, testcase := range testCases {
   170  		t.Run(testcase.Name, func(t *testing.T) {
   171  			controller := newController()
   172  			controller.addJob(testcase.job)
   173  			key := fmt.Sprintf("%s/%s", testcase.job.Namespace, testcase.job.Name)
   174  			job, err := controller.cache.Get(key)
   175  			if job == nil || err != nil {
   176  				t.Errorf("Error while Adding Job in case %d with error %s", i, err)
   177  			}
   178  			queue := controller.getWorkerQueue(key)
   179  			len := queue.Len()
   180  			if testcase.ExpectValue != len {
   181  				t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, len)
   182  			}
   183  		})
   184  	}
   185  }
   186  
   187  func TestUpdateJobFunc(t *testing.T) {
   188  	namespace := "test"
   189  
   190  	testcases := []struct {
   191  		Name   string
   192  		oldJob *batch.Job
   193  		newJob *batch.Job
   194  	}{
   195  		{
   196  			Name: "Job Update Success Case",
   197  			oldJob: &batch.Job{
   198  				ObjectMeta: metav1.ObjectMeta{
   199  					Name:            "job1",
   200  					Namespace:       namespace,
   201  					ResourceVersion: "54467984",
   202  				},
   203  				Spec: batch.JobSpec{
   204  					SchedulerName: "volcano",
   205  					MinAvailable:  5,
   206  				},
   207  				Status: batch.JobStatus{
   208  					State: batch.JobState{
   209  						Phase: batch.Pending,
   210  					},
   211  				},
   212  			},
   213  			newJob: &batch.Job{
   214  				ObjectMeta: metav1.ObjectMeta{
   215  					Name:            "job1",
   216  					Namespace:       namespace,
   217  					ResourceVersion: "54469999",
   218  				},
   219  				Spec: batch.JobSpec{
   220  					SchedulerName: "volcano",
   221  					MinAvailable:  5,
   222  				},
   223  				Status: batch.JobStatus{
   224  					State: batch.JobState{
   225  						Phase: batch.Running,
   226  					},
   227  				},
   228  			},
   229  		},
   230  		{
   231  			Name: "Job Update Failure Case",
   232  			oldJob: &batch.Job{
   233  				ObjectMeta: metav1.ObjectMeta{
   234  					Name:            "job1",
   235  					Namespace:       namespace,
   236  					ResourceVersion: "54469999",
   237  				},
   238  				Spec: batch.JobSpec{
   239  					SchedulerName: "volcano",
   240  					MinAvailable:  5,
   241  				},
   242  				Status: batch.JobStatus{
   243  					State: batch.JobState{
   244  						Phase: batch.Pending,
   245  					},
   246  				},
   247  			},
   248  			newJob: &batch.Job{
   249  				ObjectMeta: metav1.ObjectMeta{
   250  					Name:            "job1",
   251  					Namespace:       namespace,
   252  					ResourceVersion: "54469999",
   253  				},
   254  				Spec: batch.JobSpec{
   255  					SchedulerName: "volcano",
   256  					MinAvailable:  5,
   257  				},
   258  				Status: batch.JobStatus{
   259  					State: batch.JobState{
   260  						Phase: batch.Pending,
   261  					},
   262  				},
   263  			},
   264  		},
   265  	}
   266  
   267  	for i, testcase := range testcases {
   268  		t.Run(testcase.Name, func(t *testing.T) {
   269  			controller := newController()
   270  			controller.addJob(testcase.oldJob)
   271  			controller.updateJob(testcase.oldJob, testcase.newJob)
   272  			key := fmt.Sprintf("%s/%s", testcase.newJob.Namespace, testcase.newJob.Name)
   273  			job, err := controller.cache.Get(key)
   274  
   275  			if job == nil || job.Job == nil || err != nil {
   276  				t.Errorf("Error while Updating Job in case %d with error %s", i, err)
   277  			}
   278  
   279  			if job.Job.Status.State.Phase != testcase.newJob.Status.State.Phase {
   280  				t.Errorf("Error while Updating Job in case %d with error %s", i, err)
   281  			}
   282  		})
   283  	}
   284  }
   285  
   286  func TestAddPodFunc(t *testing.T) {
   287  	namespace := "test"
   288  
   289  	testcases := []struct {
   290  		Name          string
   291  		Job           *batch.Job
   292  		pods          []*v1.Pod
   293  		Annotation    map[string]string
   294  		ExpectedValue int
   295  	}{
   296  		{
   297  			Name: "AddPod Success case",
   298  			Job: &batch.Job{
   299  				ObjectMeta: metav1.ObjectMeta{
   300  					Name:      "job1",
   301  					Namespace: namespace,
   302  				},
   303  			},
   304  			pods: []*v1.Pod{
   305  				buildPod(namespace, "pod1", v1.PodPending, nil),
   306  			},
   307  			Annotation: map[string]string{
   308  				batch.JobNameKey:  "job1",
   309  				batch.JobVersion:  "0",
   310  				batch.TaskSpecKey: "task1",
   311  			},
   312  			ExpectedValue: 1,
   313  		},
   314  		{
   315  			Name: "AddPod Duplicate Pod case",
   316  			Job: &batch.Job{
   317  				ObjectMeta: metav1.ObjectMeta{
   318  					Name:      "job1",
   319  					Namespace: namespace,
   320  				},
   321  			},
   322  			pods: []*v1.Pod{
   323  				buildPod(namespace, "pod1", v1.PodPending, nil),
   324  				buildPod(namespace, "pod1", v1.PodPending, nil),
   325  			},
   326  			Annotation: map[string]string{
   327  				batch.JobNameKey:  "job1",
   328  				batch.JobVersion:  "0",
   329  				batch.TaskSpecKey: "task1",
   330  			},
   331  			ExpectedValue: 1,
   332  		},
   333  	}
   334  
   335  	for i, testcase := range testcases {
   336  
   337  		t.Run(testcase.Name, func(t *testing.T) {
   338  			controller := newController()
   339  			controller.addJob(testcase.Job)
   340  			for _, pod := range testcase.pods {
   341  				addPodAnnotation(pod, testcase.Annotation)
   342  				controller.addPod(pod)
   343  			}
   344  
   345  			key := fmt.Sprintf("%s/%s", testcase.Job.Namespace, testcase.Job.Name)
   346  			job, err := controller.cache.Get(key)
   347  
   348  			if job == nil || job.Pods == nil || err != nil {
   349  				t.Errorf("Error while Getting Job in case %d with error %s", i, err)
   350  			}
   351  
   352  			var totalPods int
   353  			for _, task := range job.Pods {
   354  				totalPods = len(task)
   355  			}
   356  			if totalPods != testcase.ExpectedValue {
   357  				t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectedValue, totalPods)
   358  			}
   359  		})
   360  	}
   361  }
   362  
   363  func TestUpdatePodFunc(t *testing.T) {
   364  	namespace := "test"
   365  
   366  	testcases := []struct {
   367  		Name          string
   368  		Job           *batch.Job
   369  		oldPod        *v1.Pod
   370  		newPod        *v1.Pod
   371  		Annotation    map[string]string
   372  		ExpectedValue v1.PodPhase
   373  	}{
   374  		{
   375  			Name: "UpdatePod Success case",
   376  			Job: &batch.Job{
   377  				ObjectMeta: metav1.ObjectMeta{
   378  					Name:      "job1",
   379  					Namespace: namespace,
   380  				},
   381  			},
   382  			oldPod: buildPod(namespace, "pod1", v1.PodPending, nil),
   383  			newPod: buildPod(namespace, "pod1", v1.PodRunning, nil),
   384  			Annotation: map[string]string{
   385  				batch.JobNameKey:  "job1",
   386  				batch.JobVersion:  "0",
   387  				batch.TaskSpecKey: "task1",
   388  			},
   389  			ExpectedValue: v1.PodRunning,
   390  		},
   391  		{
   392  			Name: "UpdatePod Failed case",
   393  			Job: &batch.Job{
   394  				ObjectMeta: metav1.ObjectMeta{
   395  					Name:      "job1",
   396  					Namespace: namespace,
   397  				},
   398  			},
   399  			oldPod: buildPod(namespace, "pod1", v1.PodPending, nil),
   400  			newPod: buildPod(namespace, "pod1", v1.PodFailed, nil),
   401  			Annotation: map[string]string{
   402  				batch.JobNameKey:  "job1",
   403  				batch.JobVersion:  "0",
   404  				batch.TaskSpecKey: "task1",
   405  			},
   406  			ExpectedValue: v1.PodFailed,
   407  		},
   408  	}
   409  
   410  	for i, testcase := range testcases {
   411  		t.Run(testcase.Name, func(t *testing.T) {
   412  			controller := newController()
   413  			controller.addJob(testcase.Job)
   414  			addPodAnnotation(testcase.oldPod, testcase.Annotation)
   415  			addPodAnnotation(testcase.newPod, testcase.Annotation)
   416  			controller.addPod(testcase.oldPod)
   417  			controller.updatePod(testcase.oldPod, testcase.newPod)
   418  
   419  			key := fmt.Sprintf("%s/%s", testcase.Job.Namespace, testcase.Job.Name)
   420  			job, err := controller.cache.Get(key)
   421  
   422  			if job == nil || job.Pods == nil || err != nil {
   423  				t.Errorf("Error while Getting Job in case %d with error %s", i, err)
   424  			}
   425  
   426  			pod := job.Pods[testcase.Annotation[batch.TaskSpecKey]][testcase.oldPod.Name]
   427  
   428  			if pod.Status.Phase != testcase.ExpectedValue {
   429  				t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectedValue, pod.Status.Phase)
   430  			}
   431  		})
   432  	}
   433  }
   434  
   435  func TestDeletePodFunc(t *testing.T) {
   436  	namespace := "test"
   437  
   438  	testcases := []struct {
   439  		Name          string
   440  		Job           *batch.Job
   441  		availablePods []*v1.Pod
   442  		deletePod     *v1.Pod
   443  		Annotation    map[string]string
   444  		ExpectedValue int
   445  	}{
   446  		{
   447  			Name: "DeletePod success case",
   448  			Job: &batch.Job{
   449  				ObjectMeta: metav1.ObjectMeta{
   450  					Name:      "job1",
   451  					Namespace: namespace,
   452  				},
   453  			},
   454  			availablePods: []*v1.Pod{
   455  				buildPod(namespace, "pod1", v1.PodRunning, nil),
   456  				buildPod(namespace, "pod2", v1.PodRunning, nil),
   457  			},
   458  			deletePod: buildPod(namespace, "pod2", v1.PodRunning, nil),
   459  			Annotation: map[string]string{
   460  				batch.JobNameKey:  "job1",
   461  				batch.JobVersion:  "0",
   462  				batch.TaskSpecKey: "task1",
   463  			},
   464  			ExpectedValue: 1,
   465  		},
   466  		{
   467  			Name: "DeletePod Pod NotAvailable case",
   468  			Job: &batch.Job{
   469  				ObjectMeta: metav1.ObjectMeta{
   470  					Name:      "job1",
   471  					Namespace: namespace,
   472  				},
   473  			},
   474  			availablePods: []*v1.Pod{
   475  				buildPod(namespace, "pod1", v1.PodRunning, nil),
   476  				buildPod(namespace, "pod2", v1.PodRunning, nil),
   477  			},
   478  			deletePod: buildPod(namespace, "pod3", v1.PodRunning, nil),
   479  			Annotation: map[string]string{
   480  				batch.JobNameKey:  "job1",
   481  				batch.JobVersion:  "0",
   482  				batch.TaskSpecKey: "task1",
   483  			},
   484  			ExpectedValue: 2,
   485  		},
   486  	}
   487  
   488  	for i, testcase := range testcases {
   489  		t.Run(testcase.Name, func(t *testing.T) {
   490  			controller := newController()
   491  			controller.addJob(testcase.Job)
   492  			for _, pod := range testcase.availablePods {
   493  				addPodAnnotation(pod, testcase.Annotation)
   494  				controller.addPod(pod)
   495  			}
   496  
   497  			addPodAnnotation(testcase.deletePod, testcase.Annotation)
   498  			controller.deletePod(testcase.deletePod)
   499  			key := fmt.Sprintf("%s/%s", testcase.Job.Namespace, testcase.Job.Name)
   500  			job, err := controller.cache.Get(key)
   501  
   502  			if job == nil || job.Pods == nil || err != nil {
   503  				t.Errorf("Error while Getting Job in case %d with error %s", i, err)
   504  			}
   505  
   506  			var totalPods int
   507  			for _, task := range job.Pods {
   508  				totalPods = len(task)
   509  			}
   510  
   511  			if totalPods != testcase.ExpectedValue {
   512  				t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectedValue, totalPods)
   513  			}
   514  		})
   515  	}
   516  }
   517  
   518  func TestUpdatePodGroupFunc(t *testing.T) {
   519  
   520  	namespace := "test"
   521  
   522  	testCases := []struct {
   523  		Name        string
   524  		oldPodGroup *scheduling.PodGroup
   525  		newPodGroup *scheduling.PodGroup
   526  		ExpectValue int
   527  	}{
   528  		{
   529  			Name: "AddCommand Success Case",
   530  			oldPodGroup: &scheduling.PodGroup{
   531  				ObjectMeta: metav1.ObjectMeta{
   532  					Name:      "pg1",
   533  					Namespace: namespace,
   534  				},
   535  				Spec: scheduling.PodGroupSpec{
   536  					MinMember: 3,
   537  				},
   538  				Status: scheduling.PodGroupStatus{
   539  					Phase: scheduling.PodGroupPending,
   540  				},
   541  			},
   542  			newPodGroup: &scheduling.PodGroup{
   543  				ObjectMeta: metav1.ObjectMeta{
   544  					Name:      "pg1",
   545  					Namespace: namespace,
   546  				},
   547  				Spec: scheduling.PodGroupSpec{
   548  					MinMember: 3,
   549  				},
   550  				Status: scheduling.PodGroupStatus{
   551  					Phase: scheduling.PodGroupRunning,
   552  				},
   553  			},
   554  			ExpectValue: 1,
   555  		},
   556  	}
   557  
   558  	for i, testcase := range testCases {
   559  
   560  		t.Run(testcase.Name, func(t *testing.T) {
   561  			controller := newController()
   562  			controller.updatePodGroup(testcase.oldPodGroup, testcase.newPodGroup)
   563  			key := fmt.Sprintf("%s/%s", testcase.oldPodGroup.Namespace, testcase.oldPodGroup.Name)
   564  			queue := controller.getWorkerQueue(key)
   565  			len := queue.Len()
   566  			if testcase.ExpectValue != len {
   567  				t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, len)
   568  			}
   569  		})
   570  	}
   571  }