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  }