volcano.sh/volcano@v1.9.0/pkg/webhooks/admission/queues/mutate/mutate_queue_test.go (about) 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 "strings" 24 "testing" 25 26 admissionv1 "k8s.io/api/admission/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 30 schedulingv1beta1 "volcano.sh/apis/pkg/apis/scheduling/v1beta1" 31 "volcano.sh/volcano/pkg/webhooks/util" 32 ) 33 34 func TestMutateQueues(t *testing.T) { 35 trueValue := true 36 stateNotSetReclaimableNotSet := schedulingv1beta1.Queue{ 37 ObjectMeta: metav1.ObjectMeta{ 38 Name: "normal-case-refresh-default-state", 39 }, 40 Spec: schedulingv1beta1.QueueSpec{ 41 Weight: 1, 42 }, 43 } 44 45 stateNotSetJSON, err := json.Marshal(stateNotSetReclaimableNotSet) 46 if err != nil { 47 t.Errorf("Marshal queue without state set failed for %v.", err) 48 } 49 50 openStateReclaimableSet := schedulingv1beta1.Queue{ 51 ObjectMeta: metav1.ObjectMeta{ 52 Name: "normal-case-set-open", 53 }, 54 Spec: schedulingv1beta1.QueueSpec{ 55 Weight: 1, 56 Reclaimable: &trueValue, 57 }, 58 Status: schedulingv1beta1.QueueStatus{ 59 State: schedulingv1beta1.QueueStateOpen, 60 }, 61 } 62 63 openStateJSON, err := json.Marshal(openStateReclaimableSet) 64 if err != nil { 65 t.Errorf("Marshal queue with open state failed for %v.", err) 66 } 67 68 pt := admissionv1.PatchTypeJSONPatch 69 70 var refreshPatch []patchOperation 71 refreshPatch = append(refreshPatch, patchOperation{ 72 Op: "add", 73 Path: "/spec/reclaimable", 74 Value: &trueValue, 75 }) 76 77 refreshPatchJSON, err := json.Marshal(refreshPatch) 78 if err != nil { 79 t.Errorf("Marshal queue patch failed for %v.", err) 80 } 81 82 hierarchyWithoutRoot := schedulingv1beta1.Queue{ 83 ObjectMeta: metav1.ObjectMeta{ 84 Name: "queue-without-root", 85 Annotations: map[string]string{ 86 schedulingv1beta1.KubeHierarchyAnnotationKey: "a/b/c", 87 schedulingv1beta1.KubeHierarchyWeightAnnotationKey: "2/3/4", 88 }, 89 }, 90 Spec: schedulingv1beta1.QueueSpec{ 91 Reclaimable: &trueValue, 92 Weight: 1, 93 }, 94 Status: schedulingv1beta1.QueueStatus{ 95 State: schedulingv1beta1.QueueStateOpen, 96 }, 97 } 98 99 hierarchyWithoutRootJSON, err := json.Marshal(hierarchyWithoutRoot) 100 if err != nil { 101 t.Errorf("Marshal hierarchy without root failed for %v.", err) 102 } 103 var appendRootPatch []patchOperation 104 appendRootPatch = append(appendRootPatch, patchOperation{ 105 Op: "add", 106 Path: fmt.Sprintf("/metadata/annotations/%s", strings.ReplaceAll(schedulingv1beta1.KubeHierarchyAnnotationKey, "/", "~1")), 107 Value: fmt.Sprintf("root/%s", hierarchyWithoutRoot.Annotations[schedulingv1beta1.KubeHierarchyAnnotationKey]), 108 }) 109 appendRootPatch = append(appendRootPatch, patchOperation{ 110 Op: "add", 111 Path: fmt.Sprintf("/metadata/annotations/%s", strings.ReplaceAll(schedulingv1beta1.KubeHierarchyWeightAnnotationKey, "/", "~1")), 112 Value: fmt.Sprintf("1/%s", hierarchyWithoutRoot.Annotations[schedulingv1beta1.KubeHierarchyWeightAnnotationKey]), 113 }) 114 appendRootPatchJSON, err := json.Marshal(appendRootPatch) 115 if err != nil { 116 t.Errorf("Marshal appendRootPatch failed for %v", err) 117 } 118 119 testCases := []struct { 120 Name string 121 AR admissionv1.AdmissionReview 122 reviewResponse *admissionv1.AdmissionResponse 123 }{ 124 { 125 Name: "Normal Case Refresh Default Open State and Reclaimable For Queue", 126 AR: admissionv1.AdmissionReview{ 127 TypeMeta: metav1.TypeMeta{ 128 Kind: "AdmissionReview", 129 APIVersion: "admission.k8s.io/v1", 130 }, 131 Request: &admissionv1.AdmissionRequest{ 132 Kind: metav1.GroupVersionKind{ 133 Group: "scheduling.volcano.sh", 134 Version: "v1beta1", 135 Kind: "Queue", 136 }, 137 Resource: metav1.GroupVersionResource{ 138 Group: "scheduling.volcano.sh", 139 Version: "v1beta1", 140 Resource: "queues", 141 }, 142 Name: "normal-case-refresh-default-state", 143 Operation: "CREATE", 144 Object: runtime.RawExtension{ 145 Raw: stateNotSetJSON, 146 }, 147 }, 148 }, 149 reviewResponse: &admissionv1.AdmissionResponse{ 150 Allowed: true, 151 PatchType: &pt, 152 Patch: refreshPatchJSON, 153 }, 154 }, 155 { 156 Name: "Invalid Action", 157 AR: admissionv1.AdmissionReview{ 158 TypeMeta: metav1.TypeMeta{ 159 Kind: "AdmissionReview", 160 APIVersion: "admission.k8s.io/v1", 161 }, 162 Request: &admissionv1.AdmissionRequest{ 163 Kind: metav1.GroupVersionKind{ 164 Group: "scheduling.volcano.sh", 165 Version: "v1beta1", 166 Kind: "Queue", 167 }, 168 Resource: metav1.GroupVersionResource{ 169 Group: "scheduling.volcano.sh", 170 Version: "v1beta1", 171 Resource: "queues", 172 }, 173 Name: "normal-case-set-open", 174 Operation: "Invalid", 175 Object: runtime.RawExtension{ 176 Raw: openStateJSON, 177 }, 178 }, 179 }, 180 reviewResponse: util.ToAdmissionResponse(fmt.Errorf("invalid operation `%s`, "+ 181 "expect operation to be `CREATE`", "Invalid")), 182 }, 183 { 184 Name: "Normal Case Append Default Root to The HDRF Attributes", 185 AR: admissionv1.AdmissionReview{ 186 TypeMeta: metav1.TypeMeta{ 187 Kind: "AdmissionReview", 188 APIVersion: "admission.k8s.io/v1", 189 }, 190 Request: &admissionv1.AdmissionRequest{ 191 Kind: metav1.GroupVersionKind{ 192 Group: "scheduling.volcano.sh", 193 Version: "v1beta1", 194 Kind: "Queue", 195 }, 196 Resource: metav1.GroupVersionResource{ 197 Group: "scheduling.volcano.sh", 198 Version: "v1beta1", 199 Resource: "queues", 200 }, 201 Name: "qeueu-hierarchy-without-root", 202 Operation: "CREATE", 203 Object: runtime.RawExtension{ 204 Raw: hierarchyWithoutRootJSON, 205 }, 206 }, 207 }, 208 reviewResponse: &admissionv1.AdmissionResponse{ 209 Allowed: true, 210 PatchType: &pt, 211 Patch: appendRootPatchJSON, 212 }, 213 }, 214 } 215 216 for _, testCase := range testCases { 217 t.Run(testCase.Name, func(t *testing.T) { 218 reviewResponse := Queues(testCase.AR) 219 if !reflect.DeepEqual(reviewResponse, testCase.reviewResponse) { 220 t.Errorf("Test case '%s' failed, expect: %v, got: %v", testCase.Name, 221 testCase.reviewResponse, reviewResponse) 222 } 223 }) 224 } 225 }