Skip to content

Commit da167ea

Browse files
Merge pull request #659 from sivanzcw/bugfix
Add admission for queue
2 parents a35595d + 303c26f commit da167ea

File tree

10 files changed

+1041
-2
lines changed

10 files changed

+1041
-2
lines changed

cmd/webhook-manager/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import (
3333
_ "volcano.sh/volcano/pkg/webhooks/admission/jobs/mutate"
3434
_ "volcano.sh/volcano/pkg/webhooks/admission/jobs/validate"
3535
_ "volcano.sh/volcano/pkg/webhooks/admission/pods"
36+
_ "volcano.sh/volcano/pkg/webhooks/admission/queues/mutate"
37+
_ "volcano.sh/volcano/pkg/webhooks/admission/queues/validate"
3638
)
3739

3840
var logFlushFreq = pflag.Duration("log-flush-frequency", 5*time.Second, "Maximum number of seconds between log flushes")

hack/.golint_failures

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ volcano.sh/volcano/pkg/scheduler/actions/enqueue
88
volcano.sh/volcano/pkg/scheduler/actions/preempt
99
volcano.sh/volcano/pkg/scheduler/actions/reclaim
1010
volcano.sh/volcano/pkg/webhooks/admission/jobs/mutate
11+
volcano.sh/volcano/pkg/webhooks/admission/queues/mutate
1112
volcano.sh/volcano/pkg/webhooks/router
1213
volcano.sh/volcano/pkg/webhooks/schema
1314
volcano.sh/volcano/test/e2e

pkg/webhooks/admission/jobs/validate/admit_job.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
k8scorevalid "k8s.io/kubernetes/pkg/apis/core/validation"
3333

3434
"volcano.sh/volcano/pkg/apis/batch/v1alpha1"
35+
schedulingv1alpha2 "volcano.sh/volcano/pkg/apis/scheduling/v1alpha2"
3536
"volcano.sh/volcano/pkg/controllers/job/plugins"
3637
"volcano.sh/volcano/pkg/webhooks/router"
3738
"volcano.sh/volcano/pkg/webhooks/schema"
@@ -177,11 +178,17 @@ func validateJob(job *v1alpha1.Job, reviewResponse *v1beta1.AdmissionResponse) s
177178
}
178179

179180
// Check whether Queue already present or not
180-
if _, err := config.VolcanoClient.SchedulingV1alpha2().Queues().Get(job.Spec.Queue, metav1.GetOptions{}); err != nil {
181+
queue, err := config.VolcanoClient.SchedulingV1alpha2().Queues().Get(job.Spec.Queue, metav1.GetOptions{})
182+
if err != nil {
181183
// TODO: deprecate v1alpha1
182184
if _, err := config.VolcanoClient.SchedulingV1alpha1().Queues().Get(job.Spec.Queue, metav1.GetOptions{}); err != nil {
183185
msg = msg + fmt.Sprintf(" unable to find job queue: %v", err)
184186
}
187+
} else {
188+
if queue.Status.State != schedulingv1alpha2.QueueStateOpen {
189+
msg = msg + fmt.Sprintf("can only submit job to queue with state `Open`, "+
190+
"queue `%s` status is `%s`", queue.Name, queue.Spec.State)
191+
}
185192
}
186193

187194
if msg != "" {

pkg/webhooks/admission/jobs/validate/admit_job_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,9 @@ func TestValidateExecution(t *testing.T) {
10441044
Spec: schedulingv1aplha2.QueueSpec{
10451045
Weight: 1,
10461046
},
1047+
Status: schedulingv1aplha2.QueueStatus{
1048+
State: schedulingv1aplha2.QueueStateOpen,
1049+
},
10471050
}
10481051
// create fake volcano clientset
10491052
config.VolcanoClient = fakeclient.NewSimpleClientset()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Copyright 2018 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 mutate
18+
19+
import (
20+
"encoding/json"
21+
"fmt"
22+
23+
"k8s.io/api/admission/v1beta1"
24+
whv1beta1 "k8s.io/api/admissionregistration/v1beta1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/klog"
27+
28+
schedulingv1alpha2 "volcano.sh/volcano/pkg/apis/scheduling/v1alpha2"
29+
"volcano.sh/volcano/pkg/webhooks/router"
30+
"volcano.sh/volcano/pkg/webhooks/schema"
31+
"volcano.sh/volcano/pkg/webhooks/util"
32+
)
33+
34+
func init() {
35+
router.RegisterAdmission(service)
36+
}
37+
38+
var service = &router.AdmissionService{
39+
Path: "/queues/mutate",
40+
Func: MutateQueues,
41+
42+
MutatingConfig: &whv1beta1.MutatingWebhookConfiguration{
43+
Webhooks: []whv1beta1.Webhook{{
44+
Name: "mutatequeue.volcano.sh",
45+
Rules: []whv1beta1.RuleWithOperations{
46+
{
47+
Operations: []whv1beta1.OperationType{whv1beta1.Create},
48+
Rule: whv1beta1.Rule{
49+
APIGroups: []string{schedulingv1alpha2.SchemeGroupVersion.Group},
50+
APIVersions: []string{schedulingv1alpha2.SchemeGroupVersion.Version},
51+
Resources: []string{"queues"},
52+
},
53+
},
54+
},
55+
}},
56+
},
57+
}
58+
59+
type patchOperation struct {
60+
Op string `json:"op"`
61+
Path string `json:"path"`
62+
Value interface{} `json:"value,omitempty"`
63+
}
64+
65+
// MutateQueues mutate queues
66+
func MutateQueues(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
67+
klog.V(3).Infof("Mutating %s queue %s.", ar.Request.Operation, ar.Request.Name)
68+
69+
queue, err := schema.DecodeQueue(ar.Request.Object, ar.Request.Resource)
70+
if err != nil {
71+
return util.ToAdmissionResponse(err)
72+
}
73+
74+
var patchBytes []byte
75+
switch ar.Request.Operation {
76+
case v1beta1.Create:
77+
patchBytes, err = createQueuePatch(queue)
78+
79+
break
80+
default:
81+
return util.ToAdmissionResponse(fmt.Errorf("invalid operation `%s`, "+
82+
"expect operation to be `CREATE`", ar.Request.Operation))
83+
}
84+
85+
if err != nil {
86+
return &v1beta1.AdmissionResponse{
87+
Allowed: false,
88+
Result: &metav1.Status{Message: err.Error()},
89+
}
90+
}
91+
92+
pt := v1beta1.PatchTypeJSONPatch
93+
return &v1beta1.AdmissionResponse{
94+
Allowed: true,
95+
Patch: patchBytes,
96+
PatchType: &pt,
97+
}
98+
}
99+
100+
func createQueuePatch(queue *schedulingv1alpha2.Queue) ([]byte, error) {
101+
var patch []patchOperation
102+
103+
if len(queue.Spec.State) == 0 {
104+
patch = append(patch, patchOperation{
105+
Op: "add",
106+
Path: "/spec/state",
107+
Value: schedulingv1alpha2.QueueStateOpen,
108+
})
109+
}
110+
111+
trueValue := true
112+
if queue.Spec.Reclaimable == nil {
113+
patch = append(patch, patchOperation{
114+
Op: "add",
115+
Path: "/spec/reclaimable",
116+
Value: &trueValue,
117+
})
118+
}
119+
120+
return json.Marshal(patch)
121+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
Copyright 2018 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 mutate
18+
19+
import (
20+
"encoding/json"
21+
"fmt"
22+
"reflect"
23+
"testing"
24+
25+
schedulingv1alpha2 "volcano.sh/volcano/pkg/apis/scheduling/v1alpha2"
26+
"volcano.sh/volcano/pkg/webhooks/util"
27+
28+
"k8s.io/api/admission/v1beta1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
)
32+
33+
func TestMutateQueues(t *testing.T) {
34+
trueValue := true
35+
stateNotSetReclaimableNotSet := schedulingv1alpha2.Queue{
36+
ObjectMeta: metav1.ObjectMeta{
37+
Name: "normal-case-refresh-default-state",
38+
},
39+
Spec: schedulingv1alpha2.QueueSpec{
40+
Weight: 1,
41+
},
42+
}
43+
44+
stateNotSetJSON, err := json.Marshal(stateNotSetReclaimableNotSet)
45+
if err != nil {
46+
t.Errorf("Marshal queue without state set failed for %v.", err)
47+
}
48+
49+
openStateReclaimableSet := schedulingv1alpha2.Queue{
50+
ObjectMeta: metav1.ObjectMeta{
51+
Name: "normal-case-set-open",
52+
},
53+
Spec: schedulingv1alpha2.QueueSpec{
54+
Weight: 1,
55+
State: schedulingv1alpha2.QueueStateOpen,
56+
Reclaimable: &trueValue,
57+
},
58+
}
59+
60+
openStateJSON, err := json.Marshal(openStateReclaimableSet)
61+
if err != nil {
62+
t.Errorf("Marshal queue with open state failed for %v.", err)
63+
}
64+
65+
pt := v1beta1.PatchTypeJSONPatch
66+
67+
var refreshPatch []patchOperation
68+
refreshPatch = append(refreshPatch, patchOperation{
69+
Op: "add",
70+
Path: "/spec/state",
71+
Value: schedulingv1alpha2.QueueStateOpen,
72+
}, patchOperation{
73+
Op: "add",
74+
Path: "/spec/reclaimable",
75+
Value: &trueValue,
76+
})
77+
78+
refreshPatchJSON, err := json.Marshal(refreshPatch)
79+
if err != nil {
80+
t.Errorf("Marshal queue patch failed for %v.", err)
81+
}
82+
83+
var openStatePatch []patchOperation
84+
openStatePatchJSON, err := json.Marshal(openStatePatch)
85+
if err != nil {
86+
t.Errorf("Marshal null patch failed for %v.", err)
87+
}
88+
89+
testCases := []struct {
90+
Name string
91+
AR v1beta1.AdmissionReview
92+
reviewResponse *v1beta1.AdmissionResponse
93+
}{
94+
{
95+
Name: "Normal Case Refresh Default Open State and Reclaimable For Queue",
96+
AR: v1beta1.AdmissionReview{
97+
TypeMeta: metav1.TypeMeta{
98+
Kind: "AdmissionReview",
99+
APIVersion: "admission.k8s.io/v1beta1",
100+
},
101+
Request: &v1beta1.AdmissionRequest{
102+
Kind: metav1.GroupVersionKind{
103+
Group: "scheduling.sigs.dev",
104+
Version: "v1alpha2",
105+
Kind: "Queue",
106+
},
107+
Resource: metav1.GroupVersionResource{
108+
Group: "scheduling.sigs.dev",
109+
Version: "v1alpha2",
110+
Resource: "queues",
111+
},
112+
Name: "normal-case-refresh-default-state",
113+
Operation: "CREATE",
114+
Object: runtime.RawExtension{
115+
Raw: stateNotSetJSON,
116+
},
117+
},
118+
},
119+
reviewResponse: &v1beta1.AdmissionResponse{
120+
Allowed: true,
121+
PatchType: &pt,
122+
Patch: refreshPatchJSON,
123+
},
124+
},
125+
{
126+
Name: "Normal Case Without Queue State or Reclaimable Patch",
127+
AR: v1beta1.AdmissionReview{
128+
TypeMeta: metav1.TypeMeta{
129+
Kind: "AdmissionReview",
130+
APIVersion: "admission.k8s.io/v1beta1",
131+
},
132+
Request: &v1beta1.AdmissionRequest{
133+
Kind: metav1.GroupVersionKind{
134+
Group: "scheduling.sigs.dev",
135+
Version: "v1alpha2",
136+
Kind: "Queue",
137+
},
138+
Resource: metav1.GroupVersionResource{
139+
Group: "scheduling.sigs.dev",
140+
Version: "v1alpha2",
141+
Resource: "queues",
142+
},
143+
Name: "normal-case-set-open",
144+
Operation: "CREATE",
145+
Object: runtime.RawExtension{
146+
Raw: openStateJSON,
147+
},
148+
},
149+
},
150+
reviewResponse: &v1beta1.AdmissionResponse{
151+
Allowed: true,
152+
PatchType: &pt,
153+
Patch: openStatePatchJSON,
154+
},
155+
},
156+
{
157+
Name: "Invalid Action",
158+
AR: v1beta1.AdmissionReview{
159+
TypeMeta: metav1.TypeMeta{
160+
Kind: "AdmissionReview",
161+
APIVersion: "admission.k8s.io/v1beta1",
162+
},
163+
Request: &v1beta1.AdmissionRequest{
164+
Kind: metav1.GroupVersionKind{
165+
Group: "scheduling.sigs.dev",
166+
Version: "v1alpha2",
167+
Kind: "Queue",
168+
},
169+
Resource: metav1.GroupVersionResource{
170+
Group: "scheduling.sigs.dev",
171+
Version: "v1alpha2",
172+
Resource: "queues",
173+
},
174+
Name: "normal-case-set-open",
175+
Operation: "Invalid",
176+
Object: runtime.RawExtension{
177+
Raw: openStateJSON,
178+
},
179+
},
180+
},
181+
reviewResponse: util.ToAdmissionResponse(fmt.Errorf("invalid operation `%s`, "+
182+
"expect operation to be `CREATE`", "Invalid")),
183+
},
184+
}
185+
186+
for _, testCase := range testCases {
187+
t.Run(testCase.Name, func(t *testing.T) {
188+
reviewResponse := MutateQueues(testCase.AR)
189+
if !reflect.DeepEqual(reviewResponse, testCase.reviewResponse) {
190+
t.Errorf("Test case %s failed, expect %v, got %v", testCase.Name,
191+
reviewResponse, testCase.reviewResponse)
192+
}
193+
})
194+
}
195+
}

0 commit comments

Comments
 (0)