volcano.sh/volcano@v1.9.0/pkg/webhooks/admission/pods/mutate/mutate_pod.go (about) 1 /* 2 Copyright 2021 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 admissionv1 "k8s.io/api/admission/v1" 24 whv1 "k8s.io/api/admissionregistration/v1" 25 v1 "k8s.io/api/core/v1" 26 "k8s.io/klog/v2" 27 28 wkconfig "volcano.sh/volcano/pkg/webhooks/config" 29 "volcano.sh/volcano/pkg/webhooks/router" 30 "volcano.sh/volcano/pkg/webhooks/schema" 31 "volcano.sh/volcano/pkg/webhooks/util" 32 ) 33 34 // patchOperation define the patch operation structure 35 type patchOperation struct { 36 Op string `json:"op"` 37 Path string `json:"path"` 38 Value interface{} `json:"value,omitempty"` 39 } 40 41 // init register mutate pod 42 func init() { 43 router.RegisterAdmission(service) 44 } 45 46 var service = &router.AdmissionService{ 47 Path: "/pods/mutate", 48 Func: Pods, 49 Config: config, 50 MutatingConfig: &whv1.MutatingWebhookConfiguration{ 51 Webhooks: []whv1.MutatingWebhook{{ 52 Name: "mutatepod.volcano.sh", 53 Rules: []whv1.RuleWithOperations{ 54 { 55 Operations: []whv1.OperationType{whv1.Create}, 56 Rule: whv1.Rule{ 57 APIGroups: []string{""}, 58 APIVersions: []string{"v1"}, 59 Resources: []string{"pods"}, 60 }, 61 }, 62 }, 63 }}, 64 }, 65 } 66 67 var config = &router.AdmissionServiceConfig{} 68 69 // Pods mutate pods. 70 func Pods(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse { 71 klog.V(3).Infof("mutating pods -- %s", ar.Request.Operation) 72 pod, err := schema.DecodePod(ar.Request.Object, ar.Request.Resource) 73 if err != nil { 74 return util.ToAdmissionResponse(err) 75 } 76 77 if pod.Namespace == "" { 78 pod.Namespace = ar.Request.Namespace 79 } 80 81 var patchBytes []byte 82 switch ar.Request.Operation { 83 case admissionv1.Create: 84 patchBytes, _ = createPatch(pod) 85 default: 86 err = fmt.Errorf("expect operation to be 'CREATE' ") 87 return util.ToAdmissionResponse(err) 88 } 89 90 reviewResponse := admissionv1.AdmissionResponse{ 91 Allowed: true, 92 Patch: patchBytes, 93 } 94 if len(patchBytes) > 0 { 95 pt := admissionv1.PatchTypeJSONPatch 96 reviewResponse.PatchType = &pt 97 } 98 99 return &reviewResponse 100 } 101 102 // createPatch patch pod 103 func createPatch(pod *v1.Pod) ([]byte, error) { 104 if config.ConfigData == nil { 105 klog.V(5).Infof("admission configuration is empty.") 106 return nil, nil 107 } 108 109 var patch []patchOperation 110 config.ConfigData.Lock() 111 defer config.ConfigData.Unlock() 112 113 for _, resourceGroup := range config.ConfigData.ResGroupsConfig { 114 klog.V(3).Infof("resourceGroup %s", resourceGroup.ResourceGroup) 115 group := GetResGroup(resourceGroup) 116 if !group.IsBelongResGroup(pod, resourceGroup) { 117 continue 118 } 119 120 patchLabel := patchLabels(pod, resourceGroup) 121 if patchLabel != nil { 122 patch = append(patch, *patchLabel) 123 } 124 125 patchAffinity := patchAffinity(pod, resourceGroup) 126 if patchAffinity != nil { 127 patch = append(patch, *patchAffinity) 128 } 129 130 patchToleration := patchTaintToleration(pod, resourceGroup) 131 if patchToleration != nil { 132 patch = append(patch, *patchToleration) 133 } 134 patchScheduler := patchSchedulerName(resourceGroup) 135 if patchScheduler != nil { 136 patch = append(patch, *patchScheduler) 137 } 138 139 klog.V(5).Infof("pod patch %v", patch) 140 return json.Marshal(patch) 141 } 142 143 return json.Marshal(patch) 144 } 145 146 // patchLabels patch label 147 func patchLabels(pod *v1.Pod, resGroupConfig wkconfig.ResGroupConfig) *patchOperation { 148 if len(resGroupConfig.Labels) == 0 { 149 return nil 150 } 151 152 nodeSelector := make(map[string]string) 153 for key, label := range pod.Spec.NodeSelector { 154 nodeSelector[key] = label 155 } 156 157 for key, label := range resGroupConfig.Labels { 158 nodeSelector[key] = label 159 } 160 161 return &patchOperation{Op: "add", Path: "/spec/nodeSelector", Value: nodeSelector} 162 } 163 164 // patchAffinity patch affinity 165 func patchAffinity(pod *v1.Pod, resGroupConfig wkconfig.ResGroupConfig) *patchOperation { 166 if resGroupConfig.Affinity == "" { 167 return nil 168 } 169 170 if pod.Spec.Affinity != nil { 171 klog.V(5).Infof("pod affinity exist: %s", pod.Name) 172 return nil 173 } 174 175 var affinity v1.Affinity 176 err := json.Unmarshal([]byte(resGroupConfig.Affinity), &affinity) 177 if err != nil { 178 fmt.Println("Failed to unmarshal JSON:", err) 179 klog.V(3).Infof("Failed to unmarshal JSON: %s", err) 180 return nil 181 } 182 183 return &patchOperation{Op: "add", Path: "/spec/affinity", Value: affinity} 184 } 185 186 // patchTaintToleration patch taint toleration 187 func patchTaintToleration(pod *v1.Pod, resGroupConfig wkconfig.ResGroupConfig) *patchOperation { 188 if len(resGroupConfig.Tolerations) == 0 { 189 return nil 190 } 191 192 var dst []v1.Toleration 193 dst = append(dst, pod.Spec.Tolerations...) 194 dst = append(dst, resGroupConfig.Tolerations...) 195 196 return &patchOperation{Op: "add", Path: "/spec/tolerations", Value: dst} 197 } 198 199 // patchSchedulerName patch scheduler 200 func patchSchedulerName(resGroupConfig wkconfig.ResGroupConfig) *patchOperation { 201 if resGroupConfig.SchedulerName == "" { 202 return nil 203 } 204 205 return &patchOperation{Op: "add", Path: "/spec/schedulerName", Value: resGroupConfig.SchedulerName} 206 }