volcano.sh/volcano@v1.9.0/pkg/webhooks/admission/queues/mutate/mutate_queue.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  	"strings"
    23  
    24  	admissionv1 "k8s.io/api/admission/v1"
    25  	whv1 "k8s.io/api/admissionregistration/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/klog/v2"
    28  
    29  	schedulingv1beta1 "volcano.sh/apis/pkg/apis/scheduling/v1beta1"
    30  	"volcano.sh/volcano/pkg/webhooks/router"
    31  	"volcano.sh/volcano/pkg/webhooks/schema"
    32  	"volcano.sh/volcano/pkg/webhooks/util"
    33  )
    34  
    35  func init() {
    36  	router.RegisterAdmission(service)
    37  }
    38  
    39  var service = &router.AdmissionService{
    40  	Path: "/queues/mutate",
    41  	Func: Queues,
    42  
    43  	MutatingConfig: &whv1.MutatingWebhookConfiguration{
    44  		Webhooks: []whv1.MutatingWebhook{{
    45  			Name: "mutatequeue.volcano.sh",
    46  			Rules: []whv1.RuleWithOperations{
    47  				{
    48  					Operations: []whv1.OperationType{whv1.Create},
    49  					Rule: whv1.Rule{
    50  						APIGroups:   []string{schedulingv1beta1.SchemeGroupVersion.Group},
    51  						APIVersions: []string{schedulingv1beta1.SchemeGroupVersion.Version},
    52  						Resources:   []string{"queues"},
    53  					},
    54  				},
    55  			},
    56  		}},
    57  	},
    58  }
    59  
    60  type patchOperation struct {
    61  	Op    string      `json:"op"`
    62  	Path  string      `json:"path"`
    63  	Value interface{} `json:"value,omitempty"`
    64  }
    65  
    66  // Queues mutate queues.
    67  func Queues(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
    68  	klog.V(3).Infof("Mutating %s queue %s.", ar.Request.Operation, ar.Request.Name)
    69  
    70  	queue, err := schema.DecodeQueue(ar.Request.Object, ar.Request.Resource)
    71  	if err != nil {
    72  		return util.ToAdmissionResponse(err)
    73  	}
    74  
    75  	var patchBytes []byte
    76  	switch ar.Request.Operation {
    77  	case admissionv1.Create:
    78  		patchBytes, err = createQueuePatch(queue)
    79  	default:
    80  		return util.ToAdmissionResponse(fmt.Errorf("invalid operation `%s`, "+
    81  			"expect operation to be `CREATE`", ar.Request.Operation))
    82  	}
    83  
    84  	if err != nil {
    85  		return &admissionv1.AdmissionResponse{
    86  			Allowed: false,
    87  			Result:  &metav1.Status{Message: err.Error()},
    88  		}
    89  	}
    90  
    91  	reviewResponse := admissionv1.AdmissionResponse{
    92  		Allowed: true,
    93  		Patch:   patchBytes,
    94  	}
    95  	if len(patchBytes) > 0 {
    96  		pt := admissionv1.PatchTypeJSONPatch
    97  		reviewResponse.PatchType = &pt
    98  	}
    99  	return &reviewResponse
   100  }
   101  
   102  func createQueuePatch(queue *schedulingv1beta1.Queue) ([]byte, error) {
   103  	var patch []patchOperation
   104  
   105  	// add root node if the root node not specified
   106  	hierarchy := queue.Annotations[schedulingv1beta1.KubeHierarchyAnnotationKey]
   107  	hierarchicalWeights := queue.Annotations[schedulingv1beta1.KubeHierarchyWeightAnnotationKey]
   108  
   109  	if hierarchy != "" && hierarchicalWeights != "" && !strings.HasPrefix(hierarchy, "root") {
   110  		// based on https://tools.ietf.org/html/rfc6901#section-3
   111  		// escape "/" with "~1"
   112  		patch = append(patch, patchOperation{
   113  			Op:    "add",
   114  			Path:  fmt.Sprintf("/metadata/annotations/%s", strings.ReplaceAll(schedulingv1beta1.KubeHierarchyAnnotationKey, "/", "~1")),
   115  			Value: fmt.Sprintf("root/%s", hierarchy),
   116  		})
   117  		patch = append(patch, patchOperation{
   118  			Op:    "add",
   119  			Path:  fmt.Sprintf("/metadata/annotations/%s", strings.ReplaceAll(schedulingv1beta1.KubeHierarchyWeightAnnotationKey, "/", "~1")),
   120  			Value: fmt.Sprintf("1/%s", hierarchicalWeights),
   121  		})
   122  	}
   123  
   124  	trueValue := true
   125  	if queue.Spec.Reclaimable == nil {
   126  		patch = append(patch, patchOperation{
   127  			Op:    "add",
   128  			Path:  "/spec/reclaimable",
   129  			Value: &trueValue,
   130  		})
   131  	}
   132  
   133  	defaultWeight := 1
   134  	if queue.Spec.Weight == 0 {
   135  		patch = append(patch, patchOperation{
   136  			Op:    "add",
   137  			Path:  "/spec/weight",
   138  			Value: &defaultWeight,
   139  		})
   140  	}
   141  
   142  	return json.Marshal(patch)
   143  }