volcano/pkg/controllers/job/job_controller_handler_test.go

563 lines
14 KiB
Go

/*
Copyright 2019 The Volcano Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package job
import (
"fmt"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/client-go/informers"
kubeclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
batch "volcano.sh/volcano/pkg/apis/batch/v1alpha1"
bus "volcano.sh/volcano/pkg/apis/bus/v1alpha1"
"volcano.sh/volcano/pkg/apis/helpers"
scheduling "volcano.sh/volcano/pkg/apis/scheduling/v1beta1"
vcclientset "volcano.sh/volcano/pkg/client/clientset/versioned"
)
func newController() *Controller {
kubeClientSet := kubeclientset.NewForConfigOrDie(&rest.Config{
Host: "",
ContentConfig: rest.ContentConfig{
GroupVersion: &v1.SchemeGroupVersion,
},
},
)
vcclient := vcclientset.NewForConfigOrDie(&rest.Config{
Host: "",
ContentConfig: rest.ContentConfig{
GroupVersion: &batch.SchemeGroupVersion,
},
})
sharedInformers := informers.NewSharedInformerFactory(kubeClientSet, 0)
controller := NewJobController(kubeClientSet, vcclient, sharedInformers, 3)
return controller
}
func buildPod(namespace, name string, p v1.PodPhase, labels map[string]string) *v1.Pod {
boolValue := true
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: types.UID(fmt.Sprintf("%v-%v", namespace, name)),
Name: name,
Namespace: namespace,
Labels: labels,
ResourceVersion: string(uuid.NewUUID()),
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: helpers.JobKind.GroupVersion().String(),
Kind: helpers.JobKind.Kind,
Controller: &boolValue,
},
},
},
Status: v1.PodStatus{
Phase: p,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "nginx",
Image: "nginx:latest",
},
},
},
}
}
func addPodAnnotation(pod *v1.Pod, annotations map[string]string) *v1.Pod {
podWithAnnotation := pod
for key, value := range annotations {
if podWithAnnotation.Annotations == nil {
podWithAnnotation.Annotations = make(map[string]string)
}
podWithAnnotation.Annotations[key] = value
}
return podWithAnnotation
}
func TestAddCommandFunc(t *testing.T) {
namespace := "test"
testCases := []struct {
Name string
command interface{}
ExpectValue int
}{
{
Name: "AddCommand Success Case",
command: &bus.Command{
ObjectMeta: metav1.ObjectMeta{
Name: "Valid Command",
Namespace: namespace,
},
},
ExpectValue: 1,
},
{
Name: "AddCommand Failure Case",
command: "Command",
ExpectValue: 0,
},
}
for i, testcase := range testCases {
t.Run(testcase.Name, func(t *testing.T) {
controller := newController()
controller.addCommand(testcase.command)
len := controller.commandQueue.Len()
if testcase.ExpectValue != len {
t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, len)
}
})
}
}
func TestJobAddFunc(t *testing.T) {
namespace := "test"
testCases := []struct {
Name string
job *batch.Job
ExpectValue int
}{
{
Name: "AddJob Success",
job: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "Job1",
Namespace: namespace,
},
},
ExpectValue: 1,
},
}
for i, testcase := range testCases {
t.Run(testcase.Name, func(t *testing.T) {
controller := newController()
controller.addJob(testcase.job)
key := fmt.Sprintf("%s/%s", testcase.job.Namespace, testcase.job.Name)
job, err := controller.cache.Get(key)
if job == nil || err != nil {
t.Errorf("Error while Adding Job in case %d with error %s", i, err)
}
queue := controller.getWorkerQueue(key)
len := queue.Len()
if testcase.ExpectValue != len {
t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, len)
}
})
}
}
func TestUpdateJobFunc(t *testing.T) {
namespace := "test"
testcases := []struct {
Name string
oldJob *batch.Job
newJob *batch.Job
}{
{
Name: "Job Update Success Case",
oldJob: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
ResourceVersion: "54467984",
},
Spec: batch.JobSpec{
SchedulerName: "volcano",
MinAvailable: 5,
},
Status: batch.JobStatus{
State: batch.JobState{
Phase: batch.Pending,
},
},
},
newJob: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
ResourceVersion: "54469999",
},
Spec: batch.JobSpec{
SchedulerName: "volcano",
MinAvailable: 5,
},
Status: batch.JobStatus{
State: batch.JobState{
Phase: batch.Running,
},
},
},
},
{
Name: "Job Update Failure Case",
oldJob: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
ResourceVersion: "54469999",
},
Spec: batch.JobSpec{
SchedulerName: "volcano",
MinAvailable: 5,
},
Status: batch.JobStatus{
State: batch.JobState{
Phase: batch.Pending,
},
},
},
newJob: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
ResourceVersion: "54469999",
},
Spec: batch.JobSpec{
SchedulerName: "volcano",
MinAvailable: 5,
},
Status: batch.JobStatus{
State: batch.JobState{
Phase: batch.Pending,
},
},
},
},
}
for i, testcase := range testcases {
t.Run(testcase.Name, func(t *testing.T) {
controller := newController()
controller.addJob(testcase.oldJob)
controller.updateJob(testcase.oldJob, testcase.newJob)
key := fmt.Sprintf("%s/%s", testcase.newJob.Namespace, testcase.newJob.Name)
job, err := controller.cache.Get(key)
if job == nil || err != nil {
t.Errorf("Error while Updating Job in case %d with error %s", i, err)
}
if job.Job.Status.State.Phase != testcase.newJob.Status.State.Phase {
t.Errorf("Error while Updating Job in case %d with error %s", i, err)
}
})
}
}
func TestAddPodFunc(t *testing.T) {
namespace := "test"
testcases := []struct {
Name string
Job *batch.Job
pods []*v1.Pod
Annotation map[string]string
ExpectedValue int
}{
{
Name: "AddPod Success case",
Job: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
},
},
pods: []*v1.Pod{
buildPod(namespace, "pod1", v1.PodPending, nil),
},
Annotation: map[string]string{
batch.JobNameKey: "job1",
batch.JobVersion: "0",
batch.TaskSpecKey: "task1",
},
ExpectedValue: 1,
},
{
Name: "AddPod Duplicate Pod case",
Job: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
},
},
pods: []*v1.Pod{
buildPod(namespace, "pod1", v1.PodPending, nil),
buildPod(namespace, "pod1", v1.PodPending, nil),
},
Annotation: map[string]string{
batch.JobNameKey: "job1",
batch.JobVersion: "0",
batch.TaskSpecKey: "task1",
},
ExpectedValue: 1,
},
}
for i, testcase := range testcases {
t.Run(testcase.Name, func(t *testing.T) {
controller := newController()
controller.addJob(testcase.Job)
for _, pod := range testcase.pods {
addPodAnnotation(pod, testcase.Annotation)
controller.addPod(pod)
}
key := fmt.Sprintf("%s/%s", testcase.Job.Namespace, testcase.Job.Name)
job, err := controller.cache.Get(key)
if job == nil || err != nil {
t.Errorf("Error while Getting Job in case %d with error %s", i, err)
}
var totalPods int
for _, task := range job.Pods {
totalPods = len(task)
}
if totalPods != testcase.ExpectedValue {
t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectedValue, totalPods)
}
})
}
}
func TestUpdatePodFunc(t *testing.T) {
namespace := "test"
testcases := []struct {
Name string
Job *batch.Job
oldPod *v1.Pod
newPod *v1.Pod
Annotation map[string]string
ExpectedValue v1.PodPhase
}{
{
Name: "UpdatePod Success case",
Job: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
},
},
oldPod: buildPod(namespace, "pod1", v1.PodPending, nil),
newPod: buildPod(namespace, "pod1", v1.PodRunning, nil),
Annotation: map[string]string{
batch.JobNameKey: "job1",
batch.JobVersion: "0",
batch.TaskSpecKey: "task1",
},
ExpectedValue: v1.PodRunning,
},
{
Name: "UpdatePod Failed case",
Job: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
},
},
oldPod: buildPod(namespace, "pod1", v1.PodPending, nil),
newPod: buildPod(namespace, "pod1", v1.PodFailed, nil),
Annotation: map[string]string{
batch.JobNameKey: "job1",
batch.JobVersion: "0",
batch.TaskSpecKey: "task1",
},
ExpectedValue: v1.PodFailed,
},
}
for i, testcase := range testcases {
t.Run(testcase.Name, func(t *testing.T) {
controller := newController()
controller.addJob(testcase.Job)
addPodAnnotation(testcase.oldPod, testcase.Annotation)
addPodAnnotation(testcase.newPod, testcase.Annotation)
controller.addPod(testcase.oldPod)
controller.updatePod(testcase.oldPod, testcase.newPod)
key := fmt.Sprintf("%s/%s", testcase.Job.Namespace, testcase.Job.Name)
job, err := controller.cache.Get(key)
if job == nil || err != nil {
t.Errorf("Error while Getting Job in case %d with error %s", i, err)
}
pod := job.Pods[testcase.Annotation[batch.TaskSpecKey]][testcase.oldPod.Name]
if pod.Status.Phase != testcase.ExpectedValue {
t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectedValue, pod.Status.Phase)
}
})
}
}
func TestDeletePodFunc(t *testing.T) {
namespace := "test"
testcases := []struct {
Name string
Job *batch.Job
availablePods []*v1.Pod
deletePod *v1.Pod
Annotation map[string]string
ExpectedValue int
}{
{
Name: "DeletePod success case",
Job: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
},
},
availablePods: []*v1.Pod{
buildPod(namespace, "pod1", v1.PodRunning, nil),
buildPod(namespace, "pod2", v1.PodRunning, nil),
},
deletePod: buildPod(namespace, "pod2", v1.PodRunning, nil),
Annotation: map[string]string{
batch.JobNameKey: "job1",
batch.JobVersion: "0",
batch.TaskSpecKey: "task1",
},
ExpectedValue: 1,
},
{
Name: "DeletePod Pod NotAvailable case",
Job: &batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "job1",
Namespace: namespace,
},
},
availablePods: []*v1.Pod{
buildPod(namespace, "pod1", v1.PodRunning, nil),
buildPod(namespace, "pod2", v1.PodRunning, nil),
},
deletePod: buildPod(namespace, "pod3", v1.PodRunning, nil),
Annotation: map[string]string{
batch.JobNameKey: "job1",
batch.JobVersion: "0",
batch.TaskSpecKey: "task1",
},
ExpectedValue: 2,
},
}
for i, testcase := range testcases {
t.Run(testcase.Name, func(t *testing.T) {
controller := newController()
controller.addJob(testcase.Job)
for _, pod := range testcase.availablePods {
addPodAnnotation(pod, testcase.Annotation)
controller.addPod(pod)
}
addPodAnnotation(testcase.deletePod, testcase.Annotation)
controller.deletePod(testcase.deletePod)
key := fmt.Sprintf("%s/%s", testcase.Job.Namespace, testcase.Job.Name)
job, err := controller.cache.Get(key)
if job == nil || err != nil {
t.Errorf("Error while Getting Job in case %d with error %s", i, err)
}
var totalPods int
for _, task := range job.Pods {
totalPods = len(task)
}
if totalPods != testcase.ExpectedValue {
t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectedValue, totalPods)
}
})
}
}
func TestUpdatePodGroupFunc(t *testing.T) {
namespace := "test"
testCases := []struct {
Name string
oldPodGroup *scheduling.PodGroup
newPodGroup *scheduling.PodGroup
ExpectValue int
}{
{
Name: "AddCommand Success Case",
oldPodGroup: &scheduling.PodGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "pg1",
Namespace: namespace,
},
Spec: scheduling.PodGroupSpec{
MinMember: 3,
},
Status: scheduling.PodGroupStatus{
Phase: scheduling.PodGroupPending,
},
},
newPodGroup: &scheduling.PodGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "pg1",
Namespace: namespace,
},
Spec: scheduling.PodGroupSpec{
MinMember: 3,
},
Status: scheduling.PodGroupStatus{
Phase: scheduling.PodGroupRunning,
},
},
ExpectValue: 1,
},
}
for i, testcase := range testCases {
t.Run(testcase.Name, func(t *testing.T) {
controller := newController()
controller.updatePodGroup(testcase.oldPodGroup, testcase.newPodGroup)
key := fmt.Sprintf("%s/%s", testcase.oldPodGroup.Namespace, testcase.oldPodGroup.Name)
queue := controller.getWorkerQueue(key)
len := queue.Len()
if testcase.ExpectValue != len {
t.Errorf("case %d (%s): expected: %v, got %v ", i, testcase.Name, testcase.ExpectValue, len)
}
})
}
}