diff --git a/pkg/admission/admit_job_test.go b/pkg/admission/admit_job_test.go index 35bb645182..b62b1a94a6 100644 --- a/pkg/admission/admit_job_test.go +++ b/pkg/admission/admit_job_test.go @@ -1,3 +1,19 @@ +/* +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 admission import ( diff --git a/pkg/admission/mutate_job.go b/pkg/admission/mutate_job.go index 6b39268586..637761f133 100644 --- a/pkg/admission/mutate_job.go +++ b/pkg/admission/mutate_job.go @@ -74,28 +74,41 @@ func MutateJobs(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { func createPatch(job v1alpha1.Job) ([]byte, error) { var patch []patchOperation - patch = append(patch, mutateSpec(job.Spec.Tasks, "/spec/tasks")...) + pathQueue := patchDefaultQueue(job) + if pathQueue != nil { + patch = append(patch, *pathQueue) + } + pathSpec := mutateSpec(job.Spec.Tasks, "/spec/tasks") + if pathSpec != nil { + patch = append(patch, *pathSpec) + } + return json.Marshal(patch) +} + +func patchDefaultQueue(job v1alpha1.Job) *patchOperation { //Add default queue if not specified. if job.Spec.Queue == "" { - patch = append(patch, patchOperation{Op: "add", Path: "/spec/queue", Value: DefaultQueue}) + return &patchOperation{Op: "add", Path: "/spec/queue", Value: DefaultQueue} } - - return json.Marshal(patch) + return nil } -func mutateSpec(tasks []v1alpha1.TaskSpec, basePath string) (patch []patchOperation) { +func mutateSpec(tasks []v1alpha1.TaskSpec, basePath string) *patchOperation { + patched := false for index := range tasks { // add default task name taskName := tasks[index].Name if len(taskName) == 0 { + patched = true tasks[index].Name = v1alpha1.DefaultTaskSpec + strconv.Itoa(index) } } - patch = append(patch, patchOperation{ + if !patched { + return nil + } + return &patchOperation{ Op: "replace", Path: basePath, Value: tasks, - }) - - return patch + } } diff --git a/pkg/admission/mutate_job_test.go b/pkg/admission/mutate_job_test.go new file mode 100644 index 0000000000..f786198b96 --- /dev/null +++ b/pkg/admission/mutate_job_test.go @@ -0,0 +1,141 @@ +/* +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 admission + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "testing" + "volcano.sh/volcano/pkg/apis/batch/v1alpha1" +) + +func TestCreatePatchExecution(t *testing.T) { + + namespace := "test" + + testCase := struct { + Name string + Job v1alpha1.Job + operation patchOperation + }{ + Name: "patch default task name", + Job: v1alpha1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "path-task-name", + Namespace: namespace, + }, + Spec: v1alpha1.JobSpec{ + MinAvailable: 1, + Tasks: []v1alpha1.TaskSpec{ + { + Replicas: 1, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"name": "test"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "fake-name", + Image: "busybox:1.24", + }, + }, + }, + }, + }, + { + Replicas: 1, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"name": "test"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "fake-name", + Image: "busybox:1.24", + }, + }, + }, + }, + }, + }, + }, + }, + operation: patchOperation{ + Op: "replace", + Path: "/spec/tasks", + Value: []v1alpha1.TaskSpec{ + { + Name: v1alpha1.DefaultTaskSpec + "0", + Replicas: 1, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"name": "test"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "fake-name", + Image: "busybox:1.24", + }, + }, + }, + }, + }, + { + Name: v1alpha1.DefaultTaskSpec + "1", + Replicas: 1, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"name": "test"}, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: "fake-name", + Image: "busybox:1.24", + }, + }, + }, + }, + }, + }, + }, + } + + ret := mutateSpec(testCase.Job.Spec.Tasks, "/spec/tasks") + if ret.Path != testCase.operation.Path || ret.Op != testCase.operation.Op { + t.Errorf("testCase %s's expected patch operation %v, but got %v", + testCase.Name, testCase.operation, *ret) + } + + actualTasks, ok := ret.Value.([]v1alpha1.TaskSpec) + if !ok { + t.Errorf("testCase '%s' path value expected to be '[]v1alpha1.TaskSpec', but negative", + testCase.Name) + } + expectedTasks, _ := testCase.operation.Value.([]v1alpha1.TaskSpec) + for index, task := range expectedTasks { + aTask := actualTasks[index] + if aTask.Name != task.Name { + t.Errorf("testCase '%s's expected patch operation with value %v, but got %v", + testCase.Name, testCase.operation.Value, ret.Value) + } + } + +} diff --git a/pkg/apis/batch/v1alpha1/job.go b/pkg/apis/batch/v1alpha1/job.go index 2c92264751..d636eabd2c 100644 --- a/pkg/apis/batch/v1alpha1/job.go +++ b/pkg/apis/batch/v1alpha1/job.go @@ -79,6 +79,10 @@ type JobSpec struct { // the Job becomes eligible to be deleted immediately after it finishes. // +optional TTLSecondsAfterFinished *int32 `json:"ttlSecondsAfterFinished,omitempty" protobuf:"varint,9,opt,name=ttlSecondsAfterFinished"` + + // If specified, indicates the job's priority. + // +optional + PriorityClassName string `json:"priorityClassName,omitempty" protobuf:"bytes,10,opt,name=priorityClassName"` } // VolumeSpec defines the specification of Volume, e.g. PVC diff --git a/pkg/controllers/job/job_controller_actions.go b/pkg/controllers/job/job_controller_actions.go index f3d2f21e37..a88382761f 100644 --- a/pkg/controllers/job/job_controller_actions.go +++ b/pkg/controllers/job/job_controller_actions.go @@ -448,9 +448,10 @@ func (cc *Controller) createPodGroupIfNotExist(job *vkv1.Job) error { }, }, Spec: kbv1.PodGroupSpec{ - MinMember: job.Spec.MinAvailable, - Queue: job.Spec.Queue, - MinResources: cc.calcPGMinResources(job), + MinMember: job.Spec.MinAvailable, + Queue: job.Spec.Queue, + MinResources: cc.calcPGMinResources(job), + PriorityClassName: job.Spec.PriorityClassName, }, }