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  }