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 }