github.com/kubewharf/katalyst-core@v0.5.3/pkg/webhook/mutating/node/node_test.go (about)

     1  /*
     2  Copyright 2022 The Katalyst 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 node
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"testing"
    23  
    24  	whcontext "github.com/slok/kubewebhook/pkg/webhook/context"
    25  	"github.com/stretchr/testify/assert"
    26  	admissionv1beta1 "k8s.io/api/admission/v1beta1"
    27  	v1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/api/resource"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  
    32  	apiconsts "github.com/kubewharf/katalyst-api/pkg/consts"
    33  	katalystbase "github.com/kubewharf/katalyst-core/cmd/base"
    34  )
    35  
    36  func TestMutateNode(t *testing.T) {
    37  	t.Parallel()
    38  
    39  	node0 := &v1.Node{
    40  		ObjectMeta: metav1.ObjectMeta{
    41  			Name:      "node0",
    42  			Namespace: "default",
    43  			Annotations: map[string]string{
    44  				apiconsts.NodeAnnotationCPUOvercommitRatioKey:    "2",
    45  				apiconsts.NodeAnnotationMemoryOvercommitRatioKey: "1.2",
    46  			},
    47  		},
    48  		Status: v1.NodeStatus{
    49  			Capacity: v1.ResourceList{
    50  				v1.ResourceCPU:    *resource.NewQuantity(48, resource.DecimalSI),
    51  				v1.ResourceMemory: resource.MustParse("192Gi"),
    52  			},
    53  			Allocatable: v1.ResourceList{
    54  				v1.ResourceCPU:    *resource.NewQuantity(44, resource.DecimalSI),
    55  				v1.ResourceMemory: resource.MustParse("186Gi"),
    56  			},
    57  		},
    58  	}
    59  	node1 := &v1.Node{
    60  		ObjectMeta: metav1.ObjectMeta{
    61  			Name:      "node1",
    62  			Namespace: "default",
    63  		},
    64  		Status: v1.NodeStatus{
    65  			Capacity: v1.ResourceList{
    66  				v1.ResourceCPU:    *resource.NewQuantity(48, resource.DecimalSI),
    67  				v1.ResourceMemory: resource.MustParse("192Gi"),
    68  			},
    69  			Allocatable: v1.ResourceList{
    70  				v1.ResourceCPU:    *resource.NewQuantity(44, resource.DecimalSI),
    71  				v1.ResourceMemory: resource.MustParse("186Gi"),
    72  			},
    73  		},
    74  	}
    75  	node2 := &v1.Node{
    76  		ObjectMeta: metav1.ObjectMeta{
    77  			Name:      "node2",
    78  			Namespace: "default",
    79  			Annotations: map[string]string{
    80  				apiconsts.NodeAnnotationCPUOvercommitRatioKey: "2",
    81  			},
    82  		},
    83  		Status: v1.NodeStatus{
    84  			Capacity: v1.ResourceList{
    85  				v1.ResourceCPU:    *resource.NewQuantity(48, resource.DecimalSI),
    86  				v1.ResourceMemory: resource.MustParse("192Gi"),
    87  			},
    88  			Allocatable: v1.ResourceList{
    89  				v1.ResourceCPU:    *resource.NewQuantity(44, resource.DecimalSI),
    90  				v1.ResourceMemory: resource.MustParse("186Gi"),
    91  			},
    92  		},
    93  	}
    94  	node3 := &v1.Node{
    95  		ObjectMeta: metav1.ObjectMeta{
    96  			Name:      "node3",
    97  			Namespace: "default",
    98  			Annotations: map[string]string{
    99  				apiconsts.NodeAnnotationMemoryOvercommitRatioKey: "1.2",
   100  			},
   101  		},
   102  		Status: v1.NodeStatus{
   103  			Capacity: v1.ResourceList{
   104  				v1.ResourceCPU:    *resource.NewQuantity(48, resource.DecimalSI),
   105  				v1.ResourceMemory: resource.MustParse("192Gi"),
   106  			},
   107  			Allocatable: v1.ResourceList{
   108  				v1.ResourceCPU:    *resource.NewQuantity(44, resource.DecimalSI),
   109  				v1.ResourceMemory: resource.MustParse("186Gi"),
   110  			},
   111  		},
   112  	}
   113  	node4 := &v1.Node{
   114  		ObjectMeta: metav1.ObjectMeta{
   115  			Name:      "node4",
   116  			Namespace: "default",
   117  			Annotations: map[string]string{
   118  				apiconsts.NodeAnnotationMemoryOvercommitRatioKey: "1.2",
   119  				apiconsts.NodeAnnotationCPUOvercommitRatioKey:    "illegal value",
   120  			},
   121  		},
   122  		Status: v1.NodeStatus{
   123  			Capacity: v1.ResourceList{
   124  				v1.ResourceCPU:    *resource.NewQuantity(48, resource.DecimalSI),
   125  				v1.ResourceMemory: resource.MustParse("192Gi"),
   126  			},
   127  			Allocatable: v1.ResourceList{
   128  				v1.ResourceCPU:    *resource.NewQuantity(44, resource.DecimalSI),
   129  				v1.ResourceMemory: resource.MustParse("186Gi"),
   130  			},
   131  		},
   132  	}
   133  	node5 := &v1.Node{
   134  		ObjectMeta: metav1.ObjectMeta{
   135  			Name:      "node5",
   136  			Namespace: "default",
   137  			Annotations: map[string]string{
   138  				"testKey": "testVal",
   139  			},
   140  		},
   141  		Status: v1.NodeStatus{
   142  			Capacity: v1.ResourceList{
   143  				v1.ResourceCPU:    *resource.NewQuantity(48, resource.DecimalSI),
   144  				v1.ResourceMemory: resource.MustParse("192Gi"),
   145  			},
   146  			Allocatable: v1.ResourceList{
   147  				v1.ResourceCPU:    *resource.NewQuantity(44, resource.DecimalSI),
   148  				v1.ResourceMemory: resource.MustParse("186Gi"),
   149  			},
   150  		},
   151  	}
   152  	node6 := &v1.Node{
   153  		ObjectMeta: metav1.ObjectMeta{
   154  			Name:      "node6",
   155  			Namespace: "default",
   156  			Annotations: map[string]string{
   157  				apiconsts.NodeAnnotationCPUOvercommitRatioKey:            "2",
   158  				apiconsts.NodeAnnotationMemoryOvercommitRatioKey:         "1.2",
   159  				apiconsts.NodeAnnotationRealtimeCPUOvercommitRatioKey:    "1",
   160  				apiconsts.NodeAnnotationRealtimeMemoryOvercommitRatioKey: "1",
   161  			},
   162  		},
   163  		Status: v1.NodeStatus{
   164  			Capacity: v1.ResourceList{
   165  				v1.ResourceCPU:    *resource.NewQuantity(48, resource.DecimalSI),
   166  				v1.ResourceMemory: resource.MustParse("192Gi"),
   167  			},
   168  			Allocatable: v1.ResourceList{
   169  				v1.ResourceCPU:    *resource.NewQuantity(44, resource.DecimalSI),
   170  				v1.ResourceMemory: resource.MustParse("186Gi"),
   171  			},
   172  		},
   173  	}
   174  	node7 := &v1.Node{
   175  		ObjectMeta: metav1.ObjectMeta{
   176  			Name:      "node6",
   177  			Namespace: "default",
   178  			Annotations: map[string]string{
   179  				apiconsts.NodeAnnotationCPUOvercommitRatioKey:            "2",
   180  				apiconsts.NodeAnnotationMemoryOvercommitRatioKey:         "1.2",
   181  				apiconsts.NodeAnnotationRealtimeCPUOvercommitRatioKey:    "2",
   182  				apiconsts.NodeAnnotationRealtimeMemoryOvercommitRatioKey: "2",
   183  				apiconsts.NodeAnnotationOvercommitAllocatableCPUKey:      "80",
   184  				apiconsts.NodeAnnotationOvercommitCapacityCPUKey:         "80",
   185  				apiconsts.NodeAnnotationOvercommitAllocatableMemoryKey:   "372Gi",
   186  				apiconsts.NodeAnnotationOvercommitCapacityMemoryKey:      "384Gi",
   187  			},
   188  		},
   189  		Status: v1.NodeStatus{
   190  			Capacity: v1.ResourceList{
   191  				v1.ResourceCPU:    *resource.NewQuantity(48, resource.DecimalSI),
   192  				v1.ResourceMemory: resource.MustParse("192Gi"),
   193  			},
   194  			Allocatable: v1.ResourceList{
   195  				v1.ResourceCPU:    *resource.NewQuantity(44, resource.DecimalSI),
   196  				v1.ResourceMemory: resource.MustParse("186Gi"),
   197  			},
   198  		},
   199  	}
   200  
   201  	cases := []struct {
   202  		name     string
   203  		review   *admissionv1beta1.AdmissionReview
   204  		expPatch []string
   205  		allow    bool
   206  	}{
   207  		{
   208  			name: "node with overcommit annotation",
   209  			review: &admissionv1beta1.AdmissionReview{
   210  				Request: &admissionv1beta1.AdmissionRequest{
   211  					UID: "case0",
   212  					Object: runtime.RawExtension{
   213  						Raw: nodeToJson(node0),
   214  					},
   215  					Operation:   admissionv1beta1.Update,
   216  					SubResource: "status",
   217  				},
   218  			},
   219  			// 44 * 2 = 88, 186 * 1024 * 1024 * 1024 * 1.2 = 239659175116.8
   220  			expPatch: []string{
   221  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_cpu","value":"44"}`,
   222  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_memory","value":"186Gi"}`,
   223  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_cpu","value":"48"}`,
   224  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_memory","value":"192Gi"}`,
   225  				`{"op":"replace","path":"/status/allocatable/cpu","value":"88"},{"op":"replace","path":"/status/allocatable/memory","value":"239659175116"}`,
   226  				`{"op":"replace","path":"/status/capacity/cpu","value":"96"},{"op":"replace","path":"/status/capacity/memory","value":"247390116249"}`,
   227  			},
   228  			allow: true,
   229  		},
   230  		{
   231  			name: "node without annotation",
   232  			review: &admissionv1beta1.AdmissionReview{
   233  				Request: &admissionv1beta1.AdmissionRequest{
   234  					UID: "case1",
   235  					Object: runtime.RawExtension{
   236  						Raw: nodeToJson(node1),
   237  					},
   238  					Operation:   admissionv1beta1.Update,
   239  					SubResource: "status",
   240  				},
   241  			},
   242  			expPatch: []string{
   243  				`{"op":"add","path":"/metadata/annotations","value":{"katalyst.kubewharf.io/original_allocatable_cpu":"44","katalyst.kubewharf.io/original_allocatable_memory":"186Gi","katalyst.kubewharf.io/original_capacity_cpu":"48","katalyst.kubewharf.io/original_capacity_memory":"192Gi"}}`,
   244  			},
   245  			allow: true,
   246  		},
   247  		{
   248  			name: "node with only CPU overcommit annotation",
   249  			review: &admissionv1beta1.AdmissionReview{
   250  				Request: &admissionv1beta1.AdmissionRequest{
   251  					UID: "case2",
   252  					Object: runtime.RawExtension{
   253  						Raw: nodeToJson(node2),
   254  					},
   255  					Operation:   admissionv1beta1.Update,
   256  					SubResource: "status",
   257  				},
   258  			},
   259  			expPatch: []string{
   260  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_cpu","value":"44"}`,
   261  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_memory","value":"186Gi"}`,
   262  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_cpu","value":"48"}`,
   263  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_memory","value":"192Gi"}`,
   264  				`{"op":"replace","path":"/status/allocatable/cpu","value":"88"}`,
   265  				`{"op":"replace","path":"/status/capacity/cpu","value":"96"}`,
   266  			},
   267  			allow: true,
   268  		},
   269  		{
   270  			name: "node with only memory overcommit annotation",
   271  			review: &admissionv1beta1.AdmissionReview{
   272  				Request: &admissionv1beta1.AdmissionRequest{
   273  					UID: "case3",
   274  					Object: runtime.RawExtension{
   275  						Raw: nodeToJson(node3),
   276  					},
   277  					Operation:   admissionv1beta1.Update,
   278  					SubResource: "status",
   279  				},
   280  			},
   281  			expPatch: []string{
   282  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_cpu","value":"44"}`,
   283  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_memory","value":"186Gi"}`,
   284  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_cpu","value":"48"}`,
   285  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_memory","value":"192Gi"}`,
   286  				`{"op":"replace","path":"/status/allocatable/memory","value":"239659175116"}`,
   287  				`{"op":"replace","path":"/status/capacity/memory","value":"247390116249"}`,
   288  			},
   289  			allow: true,
   290  		},
   291  		{
   292  			name: "node with illegal overcommit annotation",
   293  			review: &admissionv1beta1.AdmissionReview{
   294  				Request: &admissionv1beta1.AdmissionRequest{
   295  					UID: "case4",
   296  					Object: runtime.RawExtension{
   297  						Raw: nodeToJson(node4),
   298  					},
   299  					Operation:   admissionv1beta1.Update,
   300  					SubResource: "status",
   301  				},
   302  			},
   303  			expPatch: []string{
   304  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_cpu","value":"44"}`,
   305  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_memory","value":"186Gi"}`,
   306  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_cpu","value":"48"}`,
   307  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_memory","value":"192Gi"}`,
   308  				`{"op":"replace","path":"/status/allocatable/memory","value":"239659175116"}`,
   309  				`{"op":"replace","path":"/status/capacity/memory","value":"247390116249"}`,
   310  			},
   311  			allow: true,
   312  		},
   313  		{
   314  			name: "CREATE request",
   315  			review: &admissionv1beta1.AdmissionReview{
   316  				Request: &admissionv1beta1.AdmissionRequest{
   317  					UID: "case5",
   318  					Object: runtime.RawExtension{
   319  						Raw: nodeToJson(node0),
   320  					},
   321  					Operation:   admissionv1beta1.Create,
   322  					SubResource: "status",
   323  				},
   324  			},
   325  			expPatch: []string{`[]`},
   326  			allow:    true,
   327  		},
   328  		{
   329  			name: "node without overcommit annotation",
   330  			review: &admissionv1beta1.AdmissionReview{
   331  				Request: &admissionv1beta1.AdmissionRequest{
   332  					UID: "case6",
   333  					Object: runtime.RawExtension{
   334  						Raw: nodeToJson(node5),
   335  					},
   336  					Operation:   admissionv1beta1.Update,
   337  					SubResource: "status",
   338  				},
   339  			},
   340  			expPatch: []string{
   341  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_cpu","value":"44"}`,
   342  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_memory","value":"186Gi"}`,
   343  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_cpu","value":"48"}`,
   344  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_memory","value":"192Gi"}`,
   345  			},
   346  			allow: true,
   347  		},
   348  		{
   349  			name: "node with lower recommend",
   350  			review: &admissionv1beta1.AdmissionReview{
   351  				Request: &admissionv1beta1.AdmissionRequest{
   352  					UID: "case7",
   353  					Object: runtime.RawExtension{
   354  						Raw: nodeToJson(node6),
   355  					},
   356  					Operation:   admissionv1beta1.Update,
   357  					SubResource: "status",
   358  				},
   359  			},
   360  			expPatch: []string{
   361  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_cpu","value":"44"}`,
   362  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_memory","value":"186Gi"}`,
   363  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_cpu","value":"48"}`,
   364  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_memory","value":"192Gi"}`,
   365  			},
   366  			allow: true,
   367  		},
   368  		{
   369  			name: "node with allocatable",
   370  			review: &admissionv1beta1.AdmissionReview{
   371  				Request: &admissionv1beta1.AdmissionRequest{
   372  					UID: "case8",
   373  					Object: runtime.RawExtension{
   374  						Raw: nodeToJson(node7),
   375  					},
   376  					Operation:   admissionv1beta1.Update,
   377  					SubResource: "status",
   378  				},
   379  			},
   380  			expPatch: []string{
   381  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_cpu","value":"44"}`,
   382  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_allocatable_memory","value":"186Gi"}`,
   383  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_cpu","value":"48"}`,
   384  				`{"op":"add","path":"/metadata/annotations/katalyst.kubewharf.io~1original_capacity_memory","value":"192Gi"}`,
   385  				`{"op":"replace","path":"/status/allocatable/cpu","value":"80"}`,
   386  				`{"op":"replace","path":"/status/capacity/cpu","value":"80"}`,
   387  				`{"op":"replace","path":"/status/allocatable/memory","value":"372Gi"}`,
   388  				`{"op":"replace","path":"/status/capacity/memory","value":"384Gi"}`,
   389  			},
   390  			allow: true,
   391  		},
   392  	}
   393  
   394  	for _, c := range cases {
   395  		c := c
   396  		t.Run(c.name, func(t *testing.T) {
   397  			t.Parallel()
   398  
   399  			controlCtx, err := katalystbase.GenerateFakeGenericContext()
   400  			assert.NoError(t, err)
   401  
   402  			ctx := context.Background()
   403  			ctx = whcontext.SetAdmissionRequest(ctx, c.review.Request)
   404  
   405  			wh, _, err := NewWebhookNode(context.TODO(), controlCtx, nil, nil, nil)
   406  			assert.NoError(t, err)
   407  
   408  			gotResponse := wh.Review(ctx, c.review)
   409  			assert.Equal(t, gotResponse.Allowed, c.allow)
   410  			assert.Equal(t, gotResponse.UID, c.review.Request.UID)
   411  			for i := range c.expPatch {
   412  				assert.Contains(t, string(gotResponse.Patch), c.expPatch[i])
   413  			}
   414  		})
   415  	}
   416  }
   417  
   418  func nodeToJson(node *v1.Node) []byte {
   419  	nj, _ := json.Marshal(node)
   420  	return nj
   421  }