volcano.sh/volcano@v1.9.0/pkg/webhooks/admission/queues/validate/validate_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 validate 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "reflect" 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 "k8s.io/apimachinery/pkg/util/validation/field" 30 31 schedulingv1beta1 "volcano.sh/apis/pkg/apis/scheduling/v1beta1" 32 fakeclient "volcano.sh/apis/pkg/client/clientset/versioned/fake" 33 "volcano.sh/volcano/pkg/webhooks/util" 34 ) 35 36 func TestAdmitQueues(t *testing.T) { 37 38 stateNotSet := schedulingv1beta1.Queue{ 39 ObjectMeta: metav1.ObjectMeta{ 40 Name: "normal-case-not-set", 41 }, 42 Spec: schedulingv1beta1.QueueSpec{ 43 Weight: 1, 44 }, 45 } 46 47 stateNotSetJSON, err := json.Marshal(stateNotSet) 48 if err != nil { 49 t.Errorf("Marshal queue without state set failed for %v.", err) 50 } 51 52 openState := schedulingv1beta1.Queue{ 53 ObjectMeta: metav1.ObjectMeta{ 54 Name: "normal-case-set-open", 55 }, 56 Spec: schedulingv1beta1.QueueSpec{ 57 Weight: 1, 58 }, 59 Status: schedulingv1beta1.QueueStatus{ 60 State: schedulingv1beta1.QueueStateOpen, 61 }, 62 } 63 64 openStateJSON, err := json.Marshal(openState) 65 if err != nil { 66 t.Errorf("Marshal queue with open state failed for %v.", err) 67 } 68 69 closedState := schedulingv1beta1.Queue{ 70 ObjectMeta: metav1.ObjectMeta{ 71 Name: "normal-case-set-closed", 72 }, 73 Spec: schedulingv1beta1.QueueSpec{ 74 Weight: 1, 75 }, 76 Status: schedulingv1beta1.QueueStatus{ 77 State: schedulingv1beta1.QueueStateClosed, 78 }, 79 } 80 81 closedStateJSON, err := json.Marshal(closedState) 82 if err != nil { 83 t.Errorf("Marshal queue with closed state failed for %v.", err) 84 } 85 86 wrongState := schedulingv1beta1.Queue{ 87 ObjectMeta: metav1.ObjectMeta{ 88 Name: "abnormal-case", 89 }, 90 Spec: schedulingv1beta1.QueueSpec{ 91 Weight: 1, 92 }, 93 Status: schedulingv1beta1.QueueStatus{ 94 State: "wrong", 95 }, 96 } 97 98 wrongStateJSON, err := json.Marshal(wrongState) 99 if err != nil { 100 t.Errorf("Marshal queue with wrong state failed for %v.", err) 101 } 102 103 openStateForDelete := schedulingv1beta1.Queue{ 104 ObjectMeta: metav1.ObjectMeta{ 105 Name: "open-state-for-delete", 106 }, 107 Spec: schedulingv1beta1.QueueSpec{ 108 Weight: 1, 109 }, 110 Status: schedulingv1beta1.QueueStatus{ 111 State: schedulingv1beta1.QueueStateOpen, 112 }, 113 } 114 115 openStateForDeleteJSON, err := json.Marshal(openStateForDelete) 116 if err != nil { 117 t.Errorf("Marshal queue for delete with open state failed for %v.", err) 118 } 119 120 closedStateForDelete := schedulingv1beta1.Queue{ 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: "closed-state-for-delete", 123 }, 124 Spec: schedulingv1beta1.QueueSpec{ 125 Weight: 1, 126 }, 127 Status: schedulingv1beta1.QueueStatus{ 128 State: schedulingv1beta1.QueueStateClosed, 129 }, 130 } 131 132 closedStateForDeleteJSON, err := json.Marshal(closedStateForDelete) 133 if err != nil { 134 t.Errorf("Marshal queue for delete with closed state failed for %v.", err) 135 } 136 137 weightNotSet := schedulingv1beta1.Queue{ 138 ObjectMeta: metav1.ObjectMeta{ 139 Name: "weight-not-set", 140 }, 141 Spec: schedulingv1beta1.QueueSpec{}, 142 } 143 144 weightNotSetJSON, err := json.Marshal(weightNotSet) 145 if err != nil { 146 t.Errorf("Marshal queue with no weight failed for %v.", err) 147 } 148 149 negativeWeight := schedulingv1beta1.Queue{ 150 ObjectMeta: metav1.ObjectMeta{ 151 Name: "negative-weight", 152 }, 153 Spec: schedulingv1beta1.QueueSpec{ 154 Weight: -1, 155 }, 156 } 157 158 negativeWeightJSON, err := json.Marshal(negativeWeight) 159 if err != nil { 160 t.Errorf("Marshal queue with negative weight failed for %v.", err) 161 } 162 163 positiveWeightForUpdate := schedulingv1beta1.Queue{ 164 ObjectMeta: metav1.ObjectMeta{ 165 Name: "positive-weight-for-update", 166 }, 167 Spec: schedulingv1beta1.QueueSpec{ 168 Weight: 1, 169 }, 170 } 171 positiveWeightForUpdateJSON, err := json.Marshal(positiveWeightForUpdate) 172 if err != nil { 173 t.Errorf("Marshal queue with positive weight failed for %v.", err) 174 } 175 176 negativeWeightForUpdate := schedulingv1beta1.Queue{ 177 ObjectMeta: metav1.ObjectMeta{ 178 Name: "positive-weight-for-update", 179 }, 180 Spec: schedulingv1beta1.QueueSpec{ 181 Weight: -1, 182 }, 183 } 184 185 negativeWeightForUpdateJSON, err := json.Marshal(negativeWeightForUpdate) 186 if err != nil { 187 t.Errorf("Marshal queue with negative weight failed for %v.", err) 188 189 } 190 191 hierarchyWeightsDontMatch := schedulingv1beta1.Queue{ 192 ObjectMeta: metav1.ObjectMeta{ 193 Name: "hierarchy-weights-dont-match", 194 Annotations: map[string]string{ 195 schedulingv1beta1.KubeHierarchyAnnotationKey: "root/a/b", 196 schedulingv1beta1.KubeHierarchyWeightAnnotationKey: "1/2/3/4", 197 }, 198 }, 199 Spec: schedulingv1beta1.QueueSpec{ 200 Weight: 1, 201 }, 202 } 203 204 hierarchyWeightsDontMatchJSON, err := json.Marshal(hierarchyWeightsDontMatch) 205 if err != nil { 206 t.Errorf("Marshal hierarchyWeightsDontMatch failed for %v.", err) 207 } 208 209 hierarchyWeightsNegative := schedulingv1beta1.Queue{ 210 ObjectMeta: metav1.ObjectMeta{ 211 Name: "hierarchy-weights-dont-match", 212 Annotations: map[string]string{ 213 schedulingv1beta1.KubeHierarchyAnnotationKey: "root/a/b", 214 schedulingv1beta1.KubeHierarchyWeightAnnotationKey: "1/-1/3", 215 }, 216 }, 217 Spec: schedulingv1beta1.QueueSpec{ 218 Weight: 1, 219 }, 220 } 221 hierarchyWeightsNegativeJSON, err := json.Marshal(hierarchyWeightsNegative) 222 if err != nil { 223 t.Errorf("Marshal weightsFormatNegative failed for %v.", err) 224 } 225 226 weightsFormatIllegal := schedulingv1beta1.Queue{ 227 ObjectMeta: metav1.ObjectMeta{ 228 Name: "hierarchy-weights-dont-match", 229 Annotations: map[string]string{ 230 schedulingv1beta1.KubeHierarchyAnnotationKey: "root/a/b", 231 schedulingv1beta1.KubeHierarchyWeightAnnotationKey: "1/a/3", 232 }, 233 }, 234 Spec: schedulingv1beta1.QueueSpec{ 235 Weight: 1, 236 }, 237 } 238 239 weightsFormatIllegalJSON, err := json.Marshal(weightsFormatIllegal) 240 if err != nil { 241 t.Errorf("Marshal weightsFormatIllegal failed for %v.", err) 242 } 243 244 ordinaryHierchicalQueue := schedulingv1beta1.Queue{ 245 ObjectMeta: metav1.ObjectMeta{ 246 Name: "ordinary-hierarchical-queue", 247 Annotations: map[string]string{ 248 schedulingv1beta1.KubeHierarchyAnnotationKey: "root/node1/node2", 249 schedulingv1beta1.KubeHierarchyWeightAnnotationKey: "1/2/3", 250 }, 251 }, 252 Spec: schedulingv1beta1.QueueSpec{ 253 Weight: 1, 254 }, 255 } 256 257 hierarchicalQueueInSubPathOfAnotherQueue := schedulingv1beta1.Queue{ 258 ObjectMeta: metav1.ObjectMeta{ 259 Name: "hierarchical-queue-in-sub-path-of-another-queue", 260 Annotations: map[string]string{ 261 schedulingv1beta1.KubeHierarchyAnnotationKey: "root/node1", 262 schedulingv1beta1.KubeHierarchyWeightAnnotationKey: "1/4", 263 }, 264 }, 265 Spec: schedulingv1beta1.QueueSpec{ 266 Weight: 1, 267 }, 268 } 269 hierarchicalQueueInSubPathOfAnotherQueueJSON, err := json.Marshal(hierarchicalQueueInSubPathOfAnotherQueue) 270 if err != nil { 271 t.Errorf("Marshal hierarchicalQueueInSubPathOfAnotherQueue failed for %v.", err) 272 } 273 274 config.VolcanoClient = fakeclient.NewSimpleClientset() 275 _, err = config.VolcanoClient.SchedulingV1beta1().Queues().Create(context.TODO(), &openStateForDelete, metav1.CreateOptions{}) 276 if err != nil { 277 t.Errorf("Create queue with open state failed for %v.", err) 278 } 279 280 _, err = config.VolcanoClient.SchedulingV1beta1().Queues().Create(context.TODO(), &closedStateForDelete, metav1.CreateOptions{}) 281 if err != nil { 282 t.Errorf("Create queue with closed state failed for %v.", err) 283 } 284 285 _, err = config.VolcanoClient.SchedulingV1beta1().Queues().Create(context.TODO(), &ordinaryHierchicalQueue, metav1.CreateOptions{}) 286 if err != nil { 287 t.Errorf("Create hierarchical queue failed for %v.", err) 288 } 289 _, err = config.VolcanoClient.SchedulingV1beta1().Queues().Create(context.TODO(), &positiveWeightForUpdate, metav1.CreateOptions{}) 290 if err != nil { 291 t.Errorf("Crate queue with positive weight failed for %v.", err) 292 } 293 294 defer func() { 295 if err := config.VolcanoClient.SchedulingV1beta1().Queues().Delete(context.TODO(), openStateForDelete.Name, metav1.DeleteOptions{}); err != nil { 296 fmt.Printf("Delete queue with open state failed for %v.\n", err) 297 } 298 if err := config.VolcanoClient.SchedulingV1beta1().Queues().Delete(context.TODO(), closedStateForDelete.Name, metav1.DeleteOptions{}); err != nil { 299 fmt.Printf("Delete queue with closed state failed for %v.\n", err) 300 } 301 if err := config.VolcanoClient.SchedulingV1beta1().Queues().Delete(context.TODO(), ordinaryHierchicalQueue.Name, metav1.DeleteOptions{}); err != nil { 302 t.Errorf("Delete hierarchical queue failed for %v.", err) 303 } 304 }() 305 306 testCases := []struct { 307 Name string 308 AR admissionv1.AdmissionReview 309 reviewResponse *admissionv1.AdmissionResponse 310 }{ 311 { 312 Name: "Normal Case State Not Set During Creating", 313 AR: admissionv1.AdmissionReview{ 314 TypeMeta: metav1.TypeMeta{ 315 Kind: "AdmissionReview", 316 APIVersion: "admission.k8s.io/v1beta1", 317 }, 318 Request: &admissionv1.AdmissionRequest{ 319 Kind: metav1.GroupVersionKind{ 320 Group: "scheduling.volcano.sh", 321 Version: "v1beta1", 322 Kind: "Queue", 323 }, 324 Resource: metav1.GroupVersionResource{ 325 Group: "scheduling.volcano.sh", 326 Version: "v1beta1", 327 Resource: "queues", 328 }, 329 Name: "normal-case-not-set", 330 Operation: "CREATE", 331 Object: runtime.RawExtension{ 332 Raw: stateNotSetJSON, 333 }, 334 }, 335 }, 336 reviewResponse: &admissionv1.AdmissionResponse{ 337 Allowed: true, 338 }, 339 }, 340 { 341 Name: "Normal Case Set State of Open During Creating", 342 AR: admissionv1.AdmissionReview{ 343 TypeMeta: metav1.TypeMeta{ 344 Kind: "AdmissionReview", 345 APIVersion: "admission.k8s.io/v1beta1", 346 }, 347 Request: &admissionv1.AdmissionRequest{ 348 Kind: metav1.GroupVersionKind{ 349 Group: "scheduling.volcano.sh", 350 Version: "v1beta1", 351 Kind: "Queue", 352 }, 353 Resource: metav1.GroupVersionResource{ 354 Group: "scheduling.volcano.sh", 355 Version: "v1beta1", 356 Resource: "queues", 357 }, 358 Name: "normal-case-set-open", 359 Operation: "CREATE", 360 Object: runtime.RawExtension{ 361 Raw: openStateJSON, 362 }, 363 }, 364 }, 365 reviewResponse: &admissionv1.AdmissionResponse{ 366 Allowed: true, 367 }, 368 }, 369 { 370 Name: "Normal Case Set State of Closed During Creating", 371 AR: admissionv1.AdmissionReview{ 372 TypeMeta: metav1.TypeMeta{ 373 Kind: "AdmissionReview", 374 APIVersion: "admission.k8s.io/v1beta1", 375 }, 376 Request: &admissionv1.AdmissionRequest{ 377 Kind: metav1.GroupVersionKind{ 378 Group: "scheduling.volcano.sh", 379 Version: "v1beta1", 380 Kind: "Queue", 381 }, 382 Resource: metav1.GroupVersionResource{ 383 Group: "scheduling.volcano.sh", 384 Version: "v1beta1", 385 Resource: "queues", 386 }, 387 Name: "normal-case-set-closed", 388 Operation: "CREATE", 389 Object: runtime.RawExtension{ 390 Raw: closedStateJSON, 391 }, 392 }, 393 }, 394 reviewResponse: &admissionv1.AdmissionResponse{ 395 Allowed: true, 396 }, 397 }, 398 { 399 Name: "Abnormal Case Wrong State Configured During Creating", 400 AR: admissionv1.AdmissionReview{ 401 TypeMeta: metav1.TypeMeta{ 402 Kind: "AdmissionReview", 403 APIVersion: "admission.k8s.io/v1beta1", 404 }, 405 Request: &admissionv1.AdmissionRequest{ 406 Kind: metav1.GroupVersionKind{ 407 Group: "scheduling.volcano.sh", 408 Version: "v1beta1", 409 Kind: "Queue", 410 }, 411 Resource: metav1.GroupVersionResource{ 412 Group: "scheduling.volcano.sh", 413 Version: "v1beta1", 414 Resource: "queues", 415 }, 416 Name: "abnormal-case", 417 Operation: "CREATE", 418 Object: runtime.RawExtension{ 419 Raw: wrongStateJSON, 420 }, 421 }, 422 }, 423 reviewResponse: &admissionv1.AdmissionResponse{ 424 Allowed: false, 425 Result: &metav1.Status{ 426 Message: field.Invalid(field.NewPath("requestBody").Child("spec").Child("state"), 427 "wrong", fmt.Sprintf("queue state must be in %v", []schedulingv1beta1.QueueState{ 428 schedulingv1beta1.QueueStateOpen, 429 schedulingv1beta1.QueueStateClosed, 430 })).Error(), 431 }, 432 }, 433 }, 434 { 435 Name: "Normal Case Changing State From Open to Closed During Updating", 436 AR: admissionv1.AdmissionReview{ 437 TypeMeta: metav1.TypeMeta{ 438 Kind: "AdmissionReview", 439 APIVersion: "admission.k8s.io/v1beta1", 440 }, 441 Request: &admissionv1.AdmissionRequest{ 442 Kind: metav1.GroupVersionKind{ 443 Group: "scheduling.volcano.sh", 444 Version: "v1beta1", 445 Kind: "Queue", 446 }, 447 Resource: metav1.GroupVersionResource{ 448 Group: "scheduling.volcano.sh", 449 Version: "v1beta1", 450 Resource: "queues", 451 }, 452 Name: "normal-case-open-to-close-updating", 453 Operation: "UPDATE", 454 OldObject: runtime.RawExtension{ 455 Raw: openStateJSON, 456 }, 457 Object: runtime.RawExtension{ 458 Raw: closedStateJSON, 459 }, 460 }, 461 }, 462 reviewResponse: &admissionv1.AdmissionResponse{ 463 Allowed: true, 464 }, 465 }, 466 { 467 Name: "Normal Case Changing State From Closed to Open During Updating", 468 AR: admissionv1.AdmissionReview{ 469 TypeMeta: metav1.TypeMeta{ 470 Kind: "AdmissionReview", 471 APIVersion: "admission.k8s.io/v1beta1", 472 }, 473 Request: &admissionv1.AdmissionRequest{ 474 Kind: metav1.GroupVersionKind{ 475 Group: "scheduling.volcano.sh", 476 Version: "v1beta1", 477 Kind: "Queue", 478 }, 479 Resource: metav1.GroupVersionResource{ 480 Group: "scheduling.volcano.sh", 481 Version: "v1beta1", 482 Resource: "queues", 483 }, 484 Name: "normal-case-closed-to-open-updating", 485 Operation: "UPDATE", 486 OldObject: runtime.RawExtension{ 487 Raw: closedStateJSON, 488 }, 489 Object: runtime.RawExtension{ 490 Raw: openStateJSON, 491 }, 492 }, 493 }, 494 reviewResponse: &admissionv1.AdmissionResponse{ 495 Allowed: true, 496 }, 497 }, 498 { 499 Name: "Abnormal Case Changing State From Open to Wrong State During Updating", 500 AR: admissionv1.AdmissionReview{ 501 TypeMeta: metav1.TypeMeta{ 502 Kind: "AdmissionReview", 503 APIVersion: "admission.k8s.io/v1beta1", 504 }, 505 Request: &admissionv1.AdmissionRequest{ 506 Kind: metav1.GroupVersionKind{ 507 Group: "scheduling.volcano.sh", 508 Version: "v1beta1", 509 Kind: "Queue", 510 }, 511 Resource: metav1.GroupVersionResource{ 512 Group: "scheduling.volcano.sh", 513 Version: "v1beta1", 514 Resource: "queues", 515 }, 516 Name: "abnormal-case-open-to-wrong-state-updating", 517 Operation: "UPDATE", 518 OldObject: runtime.RawExtension{ 519 Raw: openStateJSON, 520 }, 521 Object: runtime.RawExtension{ 522 Raw: wrongStateJSON, 523 }, 524 }, 525 }, 526 reviewResponse: &admissionv1.AdmissionResponse{ 527 Allowed: false, 528 Result: &metav1.Status{ 529 Message: field.Invalid(field.NewPath("requestBody").Child("spec").Child("state"), 530 "wrong", fmt.Sprintf("queue state must be in %v", []schedulingv1beta1.QueueState{ 531 schedulingv1beta1.QueueStateOpen, 532 schedulingv1beta1.QueueStateClosed, 533 })).Error(), 534 }, 535 }, 536 }, 537 { 538 Name: "Normal Case Queue With Closed State Can Be Deleted", 539 AR: admissionv1.AdmissionReview{ 540 TypeMeta: metav1.TypeMeta{ 541 Kind: "AdmissionReview", 542 APIVersion: "admission.k8s.io/v1beta1", 543 }, 544 Request: &admissionv1.AdmissionRequest{ 545 Kind: metav1.GroupVersionKind{ 546 Group: "scheduling.volcano.sh", 547 Version: "v1beta1", 548 Kind: "Queue", 549 }, 550 Resource: metav1.GroupVersionResource{ 551 Group: "scheduling.volcano.sh", 552 Version: "v1beta1", 553 Resource: "queues", 554 }, 555 Name: "closed-state-for-delete", 556 Operation: "DELETE", 557 Object: runtime.RawExtension{ 558 Raw: closedStateForDeleteJSON, 559 }, 560 }, 561 }, 562 reviewResponse: &admissionv1.AdmissionResponse{ 563 Allowed: true, 564 }, 565 }, 566 { 567 Name: "Normal Case Queue With Open State Can Be Deleted (Until close queue in kubectl supported)", 568 AR: admissionv1.AdmissionReview{ 569 TypeMeta: metav1.TypeMeta{ 570 Kind: "AdmissionReview", 571 APIVersion: "admission.k8s.io/v1beta1", 572 }, 573 Request: &admissionv1.AdmissionRequest{ 574 Kind: metav1.GroupVersionKind{ 575 Group: "scheduling.volcano.sh", 576 Version: "v1beta1", 577 Kind: "Queue", 578 }, 579 Resource: metav1.GroupVersionResource{ 580 Group: "scheduling.volcano.sh", 581 Version: "v1beta1", 582 Resource: "queues", 583 }, 584 Name: "open-state-for-delete", 585 Operation: "DELETE", 586 Object: runtime.RawExtension{ 587 Raw: openStateForDeleteJSON, 588 }, 589 }, 590 }, 591 reviewResponse: &admissionv1.AdmissionResponse{ 592 Allowed: true, 593 }, 594 }, 595 { 596 Name: "Abnormal Case default Queue Can Not Be Deleted", 597 AR: admissionv1.AdmissionReview{ 598 TypeMeta: metav1.TypeMeta{ 599 Kind: "AdmissionReview", 600 APIVersion: "admission.k8s.io/v1beta1", 601 }, 602 Request: &admissionv1.AdmissionRequest{ 603 Kind: metav1.GroupVersionKind{ 604 Group: "scheduling.volcano.sh", 605 Version: "v1beta1", 606 Kind: "Queue", 607 }, 608 Resource: metav1.GroupVersionResource{ 609 Group: "scheduling.volcano.sh", 610 Version: "v1beta1", 611 Resource: "queues", 612 }, 613 Name: "default", 614 Operation: "DELETE", 615 Object: runtime.RawExtension{ 616 Raw: openStateForDeleteJSON, 617 }, 618 }, 619 }, 620 reviewResponse: &admissionv1.AdmissionResponse{ 621 Allowed: false, 622 Result: &metav1.Status{ 623 Message: fmt.Sprintf("`%s` queue can not be deleted", "default"), 624 }, 625 }, 626 }, 627 { 628 Name: "Invalid Action", 629 AR: admissionv1.AdmissionReview{ 630 TypeMeta: metav1.TypeMeta{ 631 Kind: "AdmissionReview", 632 APIVersion: "admission.k8s.io/v1beta1", 633 }, 634 Request: &admissionv1.AdmissionRequest{ 635 Kind: metav1.GroupVersionKind{ 636 Group: "scheduling.volcano.sh", 637 Version: "v1beta1", 638 Kind: "Queue", 639 }, 640 Resource: metav1.GroupVersionResource{ 641 Group: "scheduling.volcano.sh", 642 Version: "v1beta1", 643 Resource: "queues", 644 }, 645 Name: "default", 646 Operation: "Invalid", 647 Object: runtime.RawExtension{ 648 Raw: openStateForDeleteJSON, 649 }, 650 }, 651 }, 652 reviewResponse: util.ToAdmissionResponse(fmt.Errorf("invalid operation `%s`, "+ 653 "expect operation to be `CREATE`, `UPDATE` or `DELETE`", "Invalid")), 654 }, 655 { 656 Name: "Create queue without weight", 657 AR: admissionv1.AdmissionReview{ 658 TypeMeta: metav1.TypeMeta{ 659 Kind: "AdmissionReview", 660 APIVersion: "admission.k8s.io/v1beta1", 661 }, 662 Request: &admissionv1.AdmissionRequest{ 663 Kind: metav1.GroupVersionKind{ 664 Group: "scheduling.volcano.sh", 665 Version: "v1beta1", 666 Kind: "Queue", 667 }, 668 Resource: metav1.GroupVersionResource{ 669 Group: "scheduling.volcano.sh", 670 Version: "v1beta1", 671 Resource: "queues", 672 }, 673 Name: "weight-not-set", 674 Operation: "CREATE", 675 Object: runtime.RawExtension{ 676 Raw: weightNotSetJSON, 677 }, 678 }, 679 }, 680 reviewResponse: &admissionv1.AdmissionResponse{ 681 Allowed: false, 682 Result: &metav1.Status{ 683 Message: field.Invalid(field.NewPath("requestBody").Child("spec").Child("weight"), 684 0, "queue weight must be a positive integer").Error(), 685 }, 686 }, 687 }, 688 { 689 Name: "Create queue with negative weight", 690 AR: admissionv1.AdmissionReview{ 691 TypeMeta: metav1.TypeMeta{ 692 Kind: "AdmissionReview", 693 APIVersion: "admission.k8s.io/v1beta1", 694 }, 695 Request: &admissionv1.AdmissionRequest{ 696 Kind: metav1.GroupVersionKind{ 697 Group: "scheduling.volcano.sh", 698 Version: "v1beta1", 699 Kind: "Queue", 700 }, 701 Resource: metav1.GroupVersionResource{ 702 Group: "scheduling.volcano.sh", 703 Version: "v1beta1", 704 Resource: "queues", 705 }, 706 Name: "negative-weight", 707 Operation: "CREATE", 708 Object: runtime.RawExtension{ 709 Raw: negativeWeightJSON, 710 }, 711 }, 712 }, 713 reviewResponse: &admissionv1.AdmissionResponse{ 714 Allowed: false, 715 Result: &metav1.Status{ 716 Message: field.Invalid(field.NewPath("requestBody").Child("spec").Child("weight"), 717 -1, "queue weight must be a positive integer").Error(), 718 }, 719 }, 720 }, 721 { 722 Name: "Update queue with negative weight", 723 AR: admissionv1.AdmissionReview{ 724 TypeMeta: metav1.TypeMeta{ 725 Kind: "AdmissionReview", 726 APIVersion: "admission.k8s.io/v1beta1", 727 }, 728 Request: &admissionv1.AdmissionRequest{ 729 Kind: metav1.GroupVersionKind{ 730 Group: "scheduling.volcano.sh", 731 Version: "v1beta1", 732 Kind: "Queue", 733 }, 734 Resource: metav1.GroupVersionResource{ 735 Group: "scheduling.volcano.sh", 736 Version: "v1beta1", 737 Resource: "queues", 738 }, 739 Name: "positive-weight-for-update", 740 Operation: "UPDATE", 741 OldObject: runtime.RawExtension{ 742 Raw: positiveWeightForUpdateJSON, 743 }, 744 Object: runtime.RawExtension{ 745 Raw: negativeWeightForUpdateJSON, 746 }, 747 }, 748 }, 749 reviewResponse: &admissionv1.AdmissionResponse{ 750 Allowed: false, 751 Result: &metav1.Status{ 752 Message: field.Invalid(field.NewPath("requestBody").Child("spec").Child("weight"), 753 -1, "queue weight must be a positive integer").Error(), 754 }, 755 }, 756 }, 757 758 { 759 Name: "Abnormal Case Hierarchy And Weights Do Not Match", 760 AR: admissionv1.AdmissionReview{ 761 TypeMeta: metav1.TypeMeta{ 762 Kind: "AdmissionReview", 763 APIVersion: "admission.k8s.io/v1beta1", 764 }, 765 Request: &admissionv1.AdmissionRequest{ 766 Kind: metav1.GroupVersionKind{ 767 Group: "scheduling.volcano.sh", 768 Version: "v1beta1", 769 Kind: "Queue", 770 }, 771 Resource: metav1.GroupVersionResource{ 772 Group: "scheduling.volcano.sh", 773 Version: "v1beta1", 774 Resource: "queues", 775 }, 776 Name: "default", 777 Operation: "CREATE", 778 Object: runtime.RawExtension{ 779 Raw: hierarchyWeightsDontMatchJSON, 780 }, 781 }, 782 }, 783 reviewResponse: &admissionv1.AdmissionResponse{ 784 Allowed: false, 785 Result: &metav1.Status{ 786 Message: field.Invalid(field.NewPath("requestBody").Child("metadata").Child("annotations"), 787 "root/a/b", fmt.Sprintf("%s must have the same length with %s", 788 schedulingv1beta1.KubeHierarchyAnnotationKey, 789 schedulingv1beta1.KubeHierarchyWeightAnnotationKey, 790 )).Error(), 791 }, 792 }, 793 }, 794 { 795 Name: "Abnormal Case Weights Is Negative", 796 AR: admissionv1.AdmissionReview{ 797 TypeMeta: metav1.TypeMeta{ 798 Kind: "AdmissionReview", 799 APIVersion: "admission.k8s.io/v1beta1", 800 }, 801 Request: &admissionv1.AdmissionRequest{ 802 Kind: metav1.GroupVersionKind{ 803 Group: "scheduling.volcano.sh", 804 Version: "v1beta1", 805 Kind: "Queue", 806 }, 807 Resource: metav1.GroupVersionResource{ 808 Group: "scheduling.volcano.sh", 809 Version: "v1beta1", 810 Resource: "queues", 811 }, 812 Name: "default", 813 Operation: "CREATE", 814 Object: runtime.RawExtension{ 815 Raw: hierarchyWeightsNegativeJSON, 816 }, 817 }, 818 }, 819 reviewResponse: &admissionv1.AdmissionResponse{ 820 Allowed: false, 821 Result: &metav1.Status{ 822 Message: field.Invalid(field.NewPath("requestBody").Child("metadata").Child("annotations"), 823 "1/-1/3", 824 fmt.Sprintf("%s in the %s must be larger than 0", 825 "-1", "1/-1/3", 826 )).Error(), 827 }, 828 }, 829 }, 830 { 831 Name: "Abnormal Case Weights Is Format Illegal", 832 AR: admissionv1.AdmissionReview{ 833 TypeMeta: metav1.TypeMeta{ 834 Kind: "AdmissionReview", 835 APIVersion: "admission.k8s.io/v1beta1", 836 }, 837 Request: &admissionv1.AdmissionRequest{ 838 Kind: metav1.GroupVersionKind{ 839 Group: "scheduling.volcano.sh", 840 Version: "v1beta1", 841 Kind: "Queue", 842 }, 843 Resource: metav1.GroupVersionResource{ 844 Group: "scheduling.volcano.sh", 845 Version: "v1beta1", 846 Resource: "queues", 847 }, 848 Name: "default", 849 Operation: "CREATE", 850 Object: runtime.RawExtension{ 851 Raw: weightsFormatIllegalJSON, 852 }, 853 }, 854 }, 855 reviewResponse: &admissionv1.AdmissionResponse{ 856 Allowed: false, 857 Result: &metav1.Status{ 858 Message: field.Invalid(field.NewPath("requestBody").Child("metadata").Child("annotations"), 859 "1/a/3", 860 fmt.Sprintf("%s in the %s is invalid number: strconv.ParseFloat: parsing \"a\": invalid syntax", 861 "a", "1/a/3", 862 )).Error(), 863 }, 864 }, 865 }, 866 { 867 Name: "Abnormal Case Hierarchy Is In Sub Path of Another Queue", 868 AR: admissionv1.AdmissionReview{ 869 TypeMeta: metav1.TypeMeta{ 870 Kind: "AdmissionReview", 871 APIVersion: "admission.k8s.io/v1beta1", 872 }, 873 Request: &admissionv1.AdmissionRequest{ 874 Kind: metav1.GroupVersionKind{ 875 Group: "scheduling.volcano.sh", 876 Version: "v1beta1", 877 Kind: "Queue", 878 }, 879 Resource: metav1.GroupVersionResource{ 880 Group: "scheduling.volcano.sh", 881 Version: "v1beta1", 882 Resource: "queues", 883 }, 884 Name: "default", 885 Operation: "CREATE", 886 Object: runtime.RawExtension{ 887 Raw: hierarchicalQueueInSubPathOfAnotherQueueJSON, 888 }, 889 }, 890 }, 891 reviewResponse: &admissionv1.AdmissionResponse{ 892 Allowed: false, 893 Result: &metav1.Status{ 894 Message: field.Invalid(field.NewPath("requestBody").Child("metadata").Child("annotations"), 895 "root/node1", 896 fmt.Sprintf("%s is not allowed to be in the sub path of %s of queue %s", 897 "root/node1", "root/node1/node2", ordinaryHierchicalQueue.Name, 898 )).Error(), 899 }, 900 }, 901 }, 902 } 903 904 for _, testCase := range testCases { 905 t.Run(testCase.Name, func(t *testing.T) { 906 reviewResponse := AdmitQueues(testCase.AR) 907 if !reflect.DeepEqual(reviewResponse, testCase.reviewResponse) { 908 t.Errorf("Test case %s failed, expect %v, got %v", testCase.Name, 909 testCase.reviewResponse, reviewResponse) 910 } 911 }) 912 } 913 }