k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/framework/plugins/dynamicresources/structuredparameters_test.go (about) 1 /* 2 Copyright 2024 The Kubernetes 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 dynamicresources 18 19 import ( 20 "errors" 21 "sync" 22 "testing" 23 24 "github.com/stretchr/testify/assert" 25 "github.com/stretchr/testify/require" 26 27 v1 "k8s.io/api/core/v1" 28 resourceapi "k8s.io/api/resource/v1alpha2" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/labels" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/types" 33 namedresourcesmodel "k8s.io/kubernetes/pkg/scheduler/framework/plugins/dynamicresources/structured/namedresources" 34 "k8s.io/kubernetes/test/utils/ktesting" 35 "k8s.io/utils/ptr" 36 ) 37 38 func TestModel(t *testing.T) { 39 testcases := map[string]struct { 40 slices resourceSliceLister 41 claims assumeCacheLister 42 inFlight map[types.UID]resourceapi.ResourceClaimStatus 43 44 wantResources resources 45 wantErr bool 46 }{ 47 "empty": {}, 48 49 "slice-list-error": { 50 slices: sliceError("slice list error"), 51 52 wantErr: true, 53 }, 54 55 "unknown-model": { 56 slices: sliceList{ 57 &resourceapi.ResourceSlice{ 58 NodeName: "node", 59 DriverName: "driver", 60 ResourceModel: resourceapi.ResourceModel{ /* empty! */ }, 61 }, 62 }, 63 64 // Not an error. It is safe to ignore unknown resources until a claim requests them. 65 // The unknown model in that claim then triggers an error for that claim. 66 wantResources: resources{"node": map[string]ResourceModels{ 67 "driver": { 68 NamedResources: namedresourcesmodel.Model{}, 69 }, 70 }}, 71 }, 72 73 "one-instance": { 74 slices: sliceList{ 75 &resourceapi.ResourceSlice{ 76 NodeName: "node", 77 DriverName: "driver", 78 ResourceModel: resourceapi.ResourceModel{ 79 NamedResources: &resourceapi.NamedResourcesResources{ 80 Instances: []resourceapi.NamedResourcesInstance{{ 81 Name: "one", 82 Attributes: []resourceapi.NamedResourcesAttribute{{ 83 Name: "size", 84 NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{ 85 IntValue: ptr.To(int64(1)), 86 }, 87 }}, 88 }}, 89 }, 90 }, 91 }, 92 }, 93 94 wantResources: resources{"node": map[string]ResourceModels{ 95 "driver": { 96 NamedResources: namedresourcesmodel.Model{ 97 Instances: []namedresourcesmodel.InstanceAllocation{ 98 { 99 Instance: &resourceapi.NamedResourcesInstance{ 100 Name: "one", 101 Attributes: []resourceapi.NamedResourcesAttribute{{ 102 Name: "size", 103 NamedResourcesAttributeValue: resourceapi.NamedResourcesAttributeValue{ 104 IntValue: ptr.To(int64(1)), 105 }, 106 }}, 107 }, 108 }, 109 }, 110 }, 111 }, 112 }}, 113 }, 114 115 "two-instances": { 116 slices: sliceList{ 117 &resourceapi.ResourceSlice{ 118 NodeName: "node", 119 DriverName: "driver", 120 ResourceModel: resourceapi.ResourceModel{ 121 NamedResources: &resourceapi.NamedResourcesResources{ 122 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 123 }, 124 }, 125 }, 126 }, 127 128 wantResources: resources{"node": map[string]ResourceModels{ 129 "driver": { 130 NamedResources: namedresourcesmodel.Model{ 131 Instances: []namedresourcesmodel.InstanceAllocation{ 132 { 133 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 134 }, 135 { 136 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 137 }, 138 }, 139 }, 140 }, 141 }}, 142 }, 143 144 "two-nodes": { 145 slices: sliceList{ 146 &resourceapi.ResourceSlice{ 147 NodeName: "node-a", 148 DriverName: "driver", 149 ResourceModel: resourceapi.ResourceModel{ 150 NamedResources: &resourceapi.NamedResourcesResources{ 151 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 152 }, 153 }, 154 }, 155 &resourceapi.ResourceSlice{ 156 NodeName: "node-b", 157 DriverName: "driver", 158 ResourceModel: resourceapi.ResourceModel{ 159 NamedResources: &resourceapi.NamedResourcesResources{ 160 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 161 }, 162 }, 163 }, 164 }, 165 166 wantResources: resources{ 167 "node-a": map[string]ResourceModels{ 168 "driver": { 169 NamedResources: namedresourcesmodel.Model{ 170 Instances: []namedresourcesmodel.InstanceAllocation{ 171 { 172 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 173 }, 174 { 175 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 176 }, 177 }, 178 }, 179 }, 180 }, 181 "node-b": map[string]ResourceModels{ 182 "driver": { 183 NamedResources: namedresourcesmodel.Model{ 184 Instances: []namedresourcesmodel.InstanceAllocation{ 185 { 186 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 187 }, 188 { 189 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 190 }, 191 }, 192 }, 193 }, 194 }, 195 }, 196 }, 197 198 "two-nodes-two-drivers": { 199 slices: sliceList{ 200 &resourceapi.ResourceSlice{ 201 NodeName: "node-a", 202 DriverName: "driver-a", 203 ResourceModel: resourceapi.ResourceModel{ 204 NamedResources: &resourceapi.NamedResourcesResources{ 205 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 206 }, 207 }, 208 }, 209 &resourceapi.ResourceSlice{ 210 NodeName: "node-a", 211 DriverName: "driver-b", 212 ResourceModel: resourceapi.ResourceModel{ 213 NamedResources: &resourceapi.NamedResourcesResources{ 214 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 215 }, 216 }, 217 }, 218 &resourceapi.ResourceSlice{ 219 NodeName: "node-b", 220 DriverName: "driver-a", 221 ResourceModel: resourceapi.ResourceModel{ 222 NamedResources: &resourceapi.NamedResourcesResources{ 223 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 224 }, 225 }, 226 }, 227 &resourceapi.ResourceSlice{ 228 NodeName: "node-b", 229 DriverName: "driver-b", 230 ResourceModel: resourceapi.ResourceModel{ 231 NamedResources: &resourceapi.NamedResourcesResources{ 232 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 233 }, 234 }, 235 }, 236 }, 237 238 wantResources: resources{ 239 "node-a": map[string]ResourceModels{ 240 "driver-a": { 241 NamedResources: namedresourcesmodel.Model{ 242 Instances: []namedresourcesmodel.InstanceAllocation{ 243 { 244 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 245 }, 246 { 247 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 248 }, 249 }, 250 }, 251 }, 252 "driver-b": { 253 NamedResources: namedresourcesmodel.Model{ 254 Instances: []namedresourcesmodel.InstanceAllocation{ 255 { 256 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 257 }, 258 { 259 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 260 }, 261 }, 262 }, 263 }, 264 }, 265 "node-b": map[string]ResourceModels{ 266 "driver-a": { 267 NamedResources: namedresourcesmodel.Model{ 268 Instances: []namedresourcesmodel.InstanceAllocation{ 269 { 270 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 271 }, 272 { 273 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 274 }, 275 }, 276 }, 277 }, 278 "driver-b": { 279 NamedResources: namedresourcesmodel.Model{ 280 Instances: []namedresourcesmodel.InstanceAllocation{ 281 { 282 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 283 }, 284 { 285 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 286 }, 287 }, 288 }, 289 }, 290 }, 291 }, 292 }, 293 294 "in-use-simple": { 295 slices: sliceList{ 296 &resourceapi.ResourceSlice{ 297 NodeName: "node-a", 298 DriverName: "driver-a", 299 ResourceModel: resourceapi.ResourceModel{ 300 NamedResources: &resourceapi.NamedResourcesResources{ 301 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 302 }, 303 }, 304 }, 305 &resourceapi.ResourceSlice{ 306 NodeName: "node-a", 307 DriverName: "driver-b", 308 ResourceModel: resourceapi.ResourceModel{ 309 NamedResources: &resourceapi.NamedResourcesResources{ 310 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 311 }, 312 }, 313 }, 314 &resourceapi.ResourceSlice{ 315 NodeName: "node-b", 316 DriverName: "driver-a", 317 ResourceModel: resourceapi.ResourceModel{ 318 NamedResources: &resourceapi.NamedResourcesResources{ 319 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 320 }, 321 }, 322 }, 323 &resourceapi.ResourceSlice{ 324 NodeName: "node-b", 325 DriverName: "driver-b", 326 ResourceModel: resourceapi.ResourceModel{ 327 NamedResources: &resourceapi.NamedResourcesResources{ 328 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 329 }, 330 }, 331 }, 332 }, 333 334 claims: claimList{ 335 &resourceapi.ResourceClaim{ 336 /* not allocated */ 337 }, 338 &resourceapi.ResourceClaim{ 339 Status: resourceapi.ResourceClaimStatus{ 340 DriverName: "driver-a", 341 Allocation: &resourceapi.AllocationResult{ 342 ResourceHandles: []resourceapi.ResourceHandle{{ 343 // Claims not allocated via structured parameters can be ignored. 344 }}, 345 }, 346 }, 347 }, 348 &resourceapi.ResourceClaim{ 349 Status: resourceapi.ResourceClaimStatus{ 350 DriverName: "driver-a", 351 Allocation: &resourceapi.AllocationResult{ 352 ResourceHandles: []resourceapi.ResourceHandle{{ 353 DriverName: "driver-a", 354 StructuredData: &resourceapi.StructuredResourceHandle{ 355 NodeName: "node-b", 356 // Unknown allocations can be ignored. 357 Results: []resourceapi.DriverAllocationResult{{ 358 AllocationResultModel: resourceapi.AllocationResultModel{}, 359 }}, 360 }, 361 }}, 362 }, 363 }, 364 }, 365 &resourceapi.ResourceClaim{ 366 Status: resourceapi.ResourceClaimStatus{ 367 DriverName: "driver-a", 368 Allocation: &resourceapi.AllocationResult{ 369 ResourceHandles: []resourceapi.ResourceHandle{{ 370 DriverName: "driver-a", 371 StructuredData: &resourceapi.StructuredResourceHandle{ 372 NodeName: "node-a", 373 Results: []resourceapi.DriverAllocationResult{{ 374 AllocationResultModel: resourceapi.AllocationResultModel{ 375 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 376 Name: "two", 377 }, 378 }, 379 }}, 380 }, 381 }}, 382 }, 383 }, 384 }, 385 }, 386 387 wantResources: resources{ 388 "node-a": map[string]ResourceModels{ 389 "driver-a": { 390 NamedResources: namedresourcesmodel.Model{ 391 Instances: []namedresourcesmodel.InstanceAllocation{ 392 { 393 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 394 }, 395 { 396 Allocated: true, 397 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 398 }, 399 }, 400 }, 401 }, 402 "driver-b": { 403 NamedResources: namedresourcesmodel.Model{ 404 Instances: []namedresourcesmodel.InstanceAllocation{ 405 { 406 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 407 }, 408 { 409 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 410 }, 411 }, 412 }, 413 }, 414 }, 415 "node-b": map[string]ResourceModels{ 416 "driver-a": { 417 NamedResources: namedresourcesmodel.Model{ 418 Instances: []namedresourcesmodel.InstanceAllocation{ 419 { 420 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 421 }, 422 { 423 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 424 }, 425 }, 426 }, 427 }, 428 "driver-b": { 429 NamedResources: namedresourcesmodel.Model{ 430 Instances: []namedresourcesmodel.InstanceAllocation{ 431 { 432 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 433 }, 434 { 435 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 436 }, 437 }, 438 }, 439 }, 440 }, 441 }, 442 }, 443 444 "in-use-meta-driver": { 445 slices: sliceList{ 446 &resourceapi.ResourceSlice{ 447 NodeName: "node-a", 448 DriverName: "driver-a", 449 ResourceModel: resourceapi.ResourceModel{ 450 NamedResources: &resourceapi.NamedResourcesResources{ 451 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 452 }, 453 }, 454 }, 455 &resourceapi.ResourceSlice{ 456 NodeName: "node-a", 457 DriverName: "driver-b", 458 ResourceModel: resourceapi.ResourceModel{ 459 NamedResources: &resourceapi.NamedResourcesResources{ 460 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 461 }, 462 }, 463 }, 464 &resourceapi.ResourceSlice{ 465 NodeName: "node-b", 466 DriverName: "driver-a", 467 ResourceModel: resourceapi.ResourceModel{ 468 NamedResources: &resourceapi.NamedResourcesResources{ 469 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 470 }, 471 }, 472 }, 473 &resourceapi.ResourceSlice{ 474 NodeName: "node-b", 475 DriverName: "driver-b", 476 ResourceModel: resourceapi.ResourceModel{ 477 NamedResources: &resourceapi.NamedResourcesResources{ 478 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 479 }, 480 }, 481 }, 482 }, 483 484 claims: claimList{ 485 &resourceapi.ResourceClaim{ 486 Status: resourceapi.ResourceClaimStatus{ 487 DriverName: "meta-driver", 488 Allocation: &resourceapi.AllocationResult{ 489 ResourceHandles: []resourceapi.ResourceHandle{{ 490 DriverName: "driver-b", 491 StructuredData: &resourceapi.StructuredResourceHandle{ 492 NodeName: "node-b", 493 Results: []resourceapi.DriverAllocationResult{{ 494 AllocationResultModel: resourceapi.AllocationResultModel{ 495 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 496 Name: "X", 497 }, 498 }, 499 }}, 500 }, 501 }}, 502 }, 503 }, 504 }, 505 }, 506 507 wantResources: resources{ 508 "node-a": map[string]ResourceModels{ 509 "driver-a": { 510 NamedResources: namedresourcesmodel.Model{ 511 Instances: []namedresourcesmodel.InstanceAllocation{ 512 { 513 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 514 }, 515 { 516 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 517 }, 518 }, 519 }, 520 }, 521 "driver-b": { 522 NamedResources: namedresourcesmodel.Model{ 523 Instances: []namedresourcesmodel.InstanceAllocation{ 524 { 525 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 526 }, 527 { 528 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 529 }, 530 }, 531 }, 532 }, 533 }, 534 "node-b": map[string]ResourceModels{ 535 "driver-a": { 536 NamedResources: namedresourcesmodel.Model{ 537 Instances: []namedresourcesmodel.InstanceAllocation{ 538 { 539 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 540 }, 541 { 542 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 543 }, 544 }, 545 }, 546 }, 547 "driver-b": { 548 NamedResources: namedresourcesmodel.Model{ 549 Instances: []namedresourcesmodel.InstanceAllocation{ 550 { 551 Allocated: true, 552 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 553 }, 554 { 555 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 556 }, 557 }, 558 }, 559 }, 560 }, 561 }, 562 }, 563 564 "in-use-many-results": { 565 slices: sliceList{ 566 &resourceapi.ResourceSlice{ 567 NodeName: "node-a", 568 DriverName: "driver-a", 569 ResourceModel: resourceapi.ResourceModel{ 570 NamedResources: &resourceapi.NamedResourcesResources{ 571 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 572 }, 573 }, 574 }, 575 &resourceapi.ResourceSlice{ 576 NodeName: "node-a", 577 DriverName: "driver-b", 578 ResourceModel: resourceapi.ResourceModel{ 579 NamedResources: &resourceapi.NamedResourcesResources{ 580 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 581 }, 582 }, 583 }, 584 &resourceapi.ResourceSlice{ 585 NodeName: "node-b", 586 DriverName: "driver-a", 587 ResourceModel: resourceapi.ResourceModel{ 588 NamedResources: &resourceapi.NamedResourcesResources{ 589 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 590 }, 591 }, 592 }, 593 &resourceapi.ResourceSlice{ 594 NodeName: "node-b", 595 DriverName: "driver-b", 596 ResourceModel: resourceapi.ResourceModel{ 597 NamedResources: &resourceapi.NamedResourcesResources{ 598 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 599 }, 600 }, 601 }, 602 }, 603 604 claims: claimList{ 605 &resourceapi.ResourceClaim{ 606 Status: resourceapi.ResourceClaimStatus{ 607 DriverName: "driver-a", 608 Allocation: &resourceapi.AllocationResult{ 609 ResourceHandles: []resourceapi.ResourceHandle{{ 610 DriverName: "driver-a", 611 StructuredData: &resourceapi.StructuredResourceHandle{ 612 NodeName: "node-a", 613 Results: []resourceapi.DriverAllocationResult{ 614 { 615 AllocationResultModel: resourceapi.AllocationResultModel{ 616 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 617 Name: "one", 618 }, 619 }, 620 }, 621 { 622 AllocationResultModel: resourceapi.AllocationResultModel{ 623 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 624 Name: "two", 625 }, 626 }, 627 }, 628 }, 629 }, 630 }}, 631 }, 632 }, 633 }, 634 }, 635 636 wantResources: resources{ 637 "node-a": map[string]ResourceModels{ 638 "driver-a": { 639 NamedResources: namedresourcesmodel.Model{ 640 Instances: []namedresourcesmodel.InstanceAllocation{ 641 { 642 Allocated: true, 643 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 644 }, 645 { 646 Allocated: true, 647 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 648 }, 649 }, 650 }, 651 }, 652 "driver-b": { 653 NamedResources: namedresourcesmodel.Model{ 654 Instances: []namedresourcesmodel.InstanceAllocation{ 655 { 656 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 657 }, 658 { 659 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 660 }, 661 }, 662 }, 663 }, 664 }, 665 "node-b": map[string]ResourceModels{ 666 "driver-a": { 667 NamedResources: namedresourcesmodel.Model{ 668 Instances: []namedresourcesmodel.InstanceAllocation{ 669 { 670 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 671 }, 672 { 673 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 674 }, 675 }, 676 }, 677 }, 678 "driver-b": { 679 NamedResources: namedresourcesmodel.Model{ 680 Instances: []namedresourcesmodel.InstanceAllocation{ 681 { 682 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 683 }, 684 { 685 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 686 }, 687 }, 688 }, 689 }, 690 }, 691 }, 692 }, 693 694 "in-use-many-handles": { 695 slices: sliceList{ 696 &resourceapi.ResourceSlice{ 697 NodeName: "node-a", 698 DriverName: "driver-a", 699 ResourceModel: resourceapi.ResourceModel{ 700 NamedResources: &resourceapi.NamedResourcesResources{ 701 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 702 }, 703 }, 704 }, 705 &resourceapi.ResourceSlice{ 706 NodeName: "node-a", 707 DriverName: "driver-b", 708 ResourceModel: resourceapi.ResourceModel{ 709 NamedResources: &resourceapi.NamedResourcesResources{ 710 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}, {Name: "two"}}, 711 }, 712 }, 713 }, 714 &resourceapi.ResourceSlice{ 715 NodeName: "node-b", 716 DriverName: "driver-a", 717 ResourceModel: resourceapi.ResourceModel{ 718 NamedResources: &resourceapi.NamedResourcesResources{ 719 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 720 }, 721 }, 722 }, 723 &resourceapi.ResourceSlice{ 724 NodeName: "node-b", 725 DriverName: "driver-b", 726 ResourceModel: resourceapi.ResourceModel{ 727 NamedResources: &resourceapi.NamedResourcesResources{ 728 Instances: []resourceapi.NamedResourcesInstance{{Name: "X"}, {Name: "Y"}}, 729 }, 730 }, 731 }, 732 }, 733 734 claims: claimList{ 735 &resourceapi.ResourceClaim{ 736 Status: resourceapi.ResourceClaimStatus{ 737 DriverName: "meta-driver", 738 Allocation: &resourceapi.AllocationResult{ 739 ResourceHandles: []resourceapi.ResourceHandle{ 740 { 741 DriverName: "driver-a", 742 StructuredData: &resourceapi.StructuredResourceHandle{ 743 NodeName: "node-b", 744 Results: []resourceapi.DriverAllocationResult{{ 745 AllocationResultModel: resourceapi.AllocationResultModel{ 746 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 747 Name: "X", 748 }, 749 }, 750 }}, 751 }, 752 }, 753 { 754 DriverName: "driver-b", 755 StructuredData: &resourceapi.StructuredResourceHandle{ 756 NodeName: "node-b", 757 Results: []resourceapi.DriverAllocationResult{{ 758 AllocationResultModel: resourceapi.AllocationResultModel{ 759 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 760 Name: "X", 761 }, 762 }, 763 }}, 764 }, 765 }, 766 }, 767 }, 768 }, 769 }, 770 }, 771 772 wantResources: resources{ 773 "node-a": map[string]ResourceModels{ 774 "driver-a": { 775 NamedResources: namedresourcesmodel.Model{ 776 Instances: []namedresourcesmodel.InstanceAllocation{ 777 { 778 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 779 }, 780 { 781 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 782 }, 783 }, 784 }, 785 }, 786 "driver-b": { 787 NamedResources: namedresourcesmodel.Model{ 788 Instances: []namedresourcesmodel.InstanceAllocation{ 789 { 790 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 791 }, 792 { 793 Instance: &resourceapi.NamedResourcesInstance{Name: "two"}, 794 }, 795 }, 796 }, 797 }, 798 }, 799 "node-b": map[string]ResourceModels{ 800 "driver-a": { 801 NamedResources: namedresourcesmodel.Model{ 802 Instances: []namedresourcesmodel.InstanceAllocation{ 803 { 804 Allocated: true, 805 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 806 }, 807 { 808 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 809 }, 810 }, 811 }, 812 }, 813 "driver-b": { 814 NamedResources: namedresourcesmodel.Model{ 815 Instances: []namedresourcesmodel.InstanceAllocation{ 816 { 817 Allocated: true, 818 Instance: &resourceapi.NamedResourcesInstance{Name: "X"}, 819 }, 820 { 821 Instance: &resourceapi.NamedResourcesInstance{Name: "Y"}, 822 }, 823 }, 824 }, 825 }, 826 }, 827 }, 828 }, 829 830 "orphaned-allocations": { 831 claims: claimList{ 832 &resourceapi.ResourceClaim{ 833 Status: resourceapi.ResourceClaimStatus{ 834 DriverName: "meta-driver", 835 Allocation: &resourceapi.AllocationResult{ 836 ResourceHandles: []resourceapi.ResourceHandle{ 837 { 838 DriverName: "driver-a", 839 StructuredData: &resourceapi.StructuredResourceHandle{ 840 NodeName: "node-b", 841 Results: []resourceapi.DriverAllocationResult{{ 842 AllocationResultModel: resourceapi.AllocationResultModel{ 843 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 844 Name: "X", 845 }, 846 }, 847 }}, 848 }, 849 }, 850 { 851 DriverName: "driver-b", 852 StructuredData: &resourceapi.StructuredResourceHandle{ 853 NodeName: "node-b", 854 Results: []resourceapi.DriverAllocationResult{{ 855 AllocationResultModel: resourceapi.AllocationResultModel{ 856 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 857 Name: "X", 858 }, 859 }, 860 }}, 861 }, 862 }, 863 }, 864 }, 865 }, 866 }, 867 }, 868 869 wantResources: resources{ 870 "node-b": map[string]ResourceModels{}, 871 }, 872 }, 873 874 "in-flight": { 875 slices: sliceList{ 876 &resourceapi.ResourceSlice{ 877 NodeName: "node", 878 DriverName: "driver", 879 ResourceModel: resourceapi.ResourceModel{ 880 NamedResources: &resourceapi.NamedResourcesResources{ 881 Instances: []resourceapi.NamedResourcesInstance{{Name: "one"}}, 882 }, 883 }, 884 }, 885 }, 886 887 claims: claimList{ 888 &resourceapi.ResourceClaim{ 889 ObjectMeta: metav1.ObjectMeta{ 890 UID: "abc", 891 }, 892 // Allocation not recorded yet. 893 }, 894 }, 895 896 inFlight: map[types.UID]resourceapi.ResourceClaimStatus{ 897 "abc": { 898 DriverName: "driver", 899 Allocation: &resourceapi.AllocationResult{ 900 ResourceHandles: []resourceapi.ResourceHandle{{ 901 DriverName: "driver", 902 StructuredData: &resourceapi.StructuredResourceHandle{ 903 NodeName: "node", 904 Results: []resourceapi.DriverAllocationResult{{ 905 AllocationResultModel: resourceapi.AllocationResultModel{ 906 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 907 Name: "one", 908 }, 909 }, 910 }}, 911 }, 912 }}, 913 }, 914 }, 915 }, 916 917 wantResources: resources{"node": map[string]ResourceModels{ 918 "driver": { 919 NamedResources: namedresourcesmodel.Model{ 920 Instances: []namedresourcesmodel.InstanceAllocation{ 921 { 922 Allocated: true, 923 Instance: &resourceapi.NamedResourcesInstance{Name: "one"}, 924 }, 925 }, 926 }, 927 }, 928 }}, 929 }, 930 } 931 932 for name, tc := range testcases { 933 t.Run(name, func(t *testing.T) { 934 tCtx := ktesting.Init(t) 935 936 var inFlightAllocations sync.Map 937 for uid, claimStatus := range tc.inFlight { 938 inFlightAllocations.Store(uid, &resourceapi.ResourceClaim{Status: claimStatus}) 939 } 940 941 slices := tc.slices 942 if slices == nil { 943 slices = sliceList{} 944 } 945 claims := tc.claims 946 if claims == nil { 947 claims = claimList{} 948 } 949 actualResources, actualErr := newResourceModel(tCtx.Logger(), slices, claims, &inFlightAllocations) 950 951 if actualErr != nil { 952 if !tc.wantErr { 953 tCtx.Fatalf("unexpected error: %v", actualErr) 954 } 955 return 956 } 957 if tc.wantErr { 958 tCtx.Fatalf("did not get expected error") 959 } 960 961 expectResources := tc.wantResources 962 if expectResources == nil { 963 expectResources = resources{} 964 } 965 require.Equal(tCtx, expectResources, actualResources) 966 }) 967 } 968 } 969 970 type sliceList []*resourceapi.ResourceSlice 971 972 func (l sliceList) List(selector labels.Selector) ([]*resourceapi.ResourceSlice, error) { 973 return l, nil 974 } 975 976 type sliceError string 977 978 func (l sliceError) List(selector labels.Selector) ([]*resourceapi.ResourceSlice, error) { 979 return nil, errors.New(string(l)) 980 } 981 982 type claimList []any 983 984 func (l claimList) List(indexObj any) []any { 985 return l 986 } 987 988 func TestController(t *testing.T) { 989 driver1 := "driver-1" 990 class1 := &resourceapi.ResourceClass{ 991 DriverName: driver1, 992 } 993 994 classParametersEmpty := &resourceapi.ResourceClassParameters{} 995 classParametersAny := &resourceapi.ResourceClassParameters{ 996 Filters: []resourceapi.ResourceFilter{{ 997 DriverName: driver1, 998 ResourceFilterModel: resourceapi.ResourceFilterModel{ 999 NamedResources: &resourceapi.NamedResourcesFilter{ 1000 Selector: "true", 1001 }, 1002 }, 1003 }}, 1004 } 1005 1006 claimParametersEmpty := &resourceapi.ResourceClaimParameters{} 1007 claimParametersAny := &resourceapi.ResourceClaimParameters{ 1008 DriverRequests: []resourceapi.DriverRequests{{ 1009 DriverName: driver1, 1010 }}, 1011 } 1012 claimParametersOne := &resourceapi.ResourceClaimParameters{ 1013 DriverRequests: []resourceapi.DriverRequests{{ 1014 DriverName: driver1, 1015 Requests: []resourceapi.ResourceRequest{{ 1016 ResourceRequestModel: resourceapi.ResourceRequestModel{ 1017 NamedResources: &resourceapi.NamedResourcesRequest{ 1018 Selector: "true", 1019 }, 1020 }, 1021 }}, 1022 }}, 1023 } 1024 claimParametersBroken := &resourceapi.ResourceClaimParameters{ 1025 DriverRequests: []resourceapi.DriverRequests{{ 1026 DriverName: driver1, 1027 Requests: []resourceapi.ResourceRequest{{ 1028 ResourceRequestModel: resourceapi.ResourceRequestModel{ 1029 NamedResources: &resourceapi.NamedResourcesRequest{ 1030 Selector: `attributes.bool["no-such-attribute"]`, 1031 }, 1032 }, 1033 }}, 1034 }}, 1035 } 1036 1037 instance1 := "instance-1" 1038 1039 node1 := "node-1" 1040 node1Selector := &v1.NodeSelector{ 1041 NodeSelectorTerms: []v1.NodeSelectorTerm{{ 1042 MatchExpressions: []v1.NodeSelectorRequirement{{ 1043 Key: "kubernetes.io/hostname", 1044 Operator: v1.NodeSelectorOpIn, 1045 Values: []string{node1}, 1046 }}, 1047 }}, 1048 } 1049 node1Resources := resources{node1: map[string]ResourceModels{ 1050 driver1: { 1051 NamedResources: namedresourcesmodel.Model{ 1052 Instances: []namedresourcesmodel.InstanceAllocation{{ 1053 Instance: &resourceapi.NamedResourcesInstance{ 1054 Name: instance1, 1055 }, 1056 }}, 1057 }, 1058 }, 1059 }} 1060 node1Allocation := &resourceapi.AllocationResult{ 1061 AvailableOnNodes: node1Selector, 1062 } 1063 1064 instance1Allocation := &resourceapi.AllocationResult{ 1065 AvailableOnNodes: node1Selector, 1066 ResourceHandles: []resourceapi.ResourceHandle{{ 1067 DriverName: driver1, 1068 StructuredData: &resourceapi.StructuredResourceHandle{ 1069 NodeName: node1, 1070 Results: []resourceapi.DriverAllocationResult{{ 1071 AllocationResultModel: resourceapi.AllocationResultModel{ 1072 NamedResources: &resourceapi.NamedResourcesAllocationResult{ 1073 Name: instance1, 1074 }, 1075 }, 1076 }}, 1077 }, 1078 }}, 1079 } 1080 1081 type nodeResult struct { 1082 isSuitable bool 1083 suitableErr string 1084 1085 driverName string 1086 allocation *resourceapi.AllocationResult 1087 allocateErr string 1088 } 1089 type nodeResults map[string]nodeResult 1090 1091 testcases := map[string]struct { 1092 resources resources 1093 class *resourceapi.ResourceClass 1094 classParameters *resourceapi.ResourceClassParameters 1095 claimParameters *resourceapi.ResourceClaimParameters 1096 1097 expectCreateErr bool 1098 expectNodeResults nodeResults 1099 }{ 1100 "empty": { 1101 class: class1, 1102 classParameters: classParametersEmpty, 1103 claimParameters: claimParametersEmpty, 1104 1105 expectNodeResults: nodeResults{ 1106 node1: {isSuitable: true, driverName: driver1, allocation: node1Allocation}, 1107 }, 1108 }, 1109 1110 "any": { 1111 class: class1, 1112 classParameters: classParametersEmpty, 1113 claimParameters: claimParametersAny, 1114 1115 expectNodeResults: nodeResults{ 1116 node1: {isSuitable: true, driverName: driver1, allocation: node1Allocation}, 1117 }, 1118 }, 1119 1120 "missing-model": { 1121 class: class1, 1122 classParameters: classParametersEmpty, 1123 claimParameters: &resourceapi.ResourceClaimParameters{ 1124 DriverRequests: []resourceapi.DriverRequests{{ 1125 Requests: []resourceapi.ResourceRequest{{ /* empty model */ }}, 1126 }}, 1127 }, 1128 1129 expectCreateErr: true, 1130 }, 1131 1132 "no-resources": { 1133 class: class1, 1134 classParameters: classParametersEmpty, 1135 claimParameters: claimParametersOne, 1136 1137 expectNodeResults: nodeResults{ 1138 node1: {isSuitable: false, allocateErr: "allocating via named resources structured model: insufficient resources"}, 1139 }, 1140 }, 1141 1142 "have-resources": { 1143 resources: node1Resources, 1144 class: class1, 1145 classParameters: classParametersEmpty, 1146 claimParameters: claimParametersOne, 1147 1148 expectNodeResults: nodeResults{ 1149 node1: {isSuitable: true, driverName: driver1, allocation: instance1Allocation}, 1150 }, 1151 }, 1152 1153 "broken-cel": { 1154 resources: node1Resources, 1155 class: class1, 1156 classParameters: classParametersEmpty, 1157 claimParameters: claimParametersBroken, 1158 1159 expectNodeResults: nodeResults{ 1160 node1: {suitableErr: `checking node "node-1" and resources of driver "driver-1": evaluate request CEL expression: no such key: no-such-attribute`}, 1161 }, 1162 }, 1163 1164 "class-filter": { 1165 resources: node1Resources, 1166 class: class1, 1167 classParameters: classParametersAny, 1168 claimParameters: claimParametersOne, 1169 1170 expectNodeResults: nodeResults{ 1171 node1: {isSuitable: true, driverName: driver1, allocation: instance1Allocation}, 1172 }, 1173 }, 1174 1175 "vendor-parameters": { 1176 resources: node1Resources, 1177 class: class1, 1178 classParameters: func() *resourceapi.ResourceClassParameters { 1179 parameters := classParametersAny.DeepCopy() 1180 parameters.VendorParameters = []resourceapi.VendorParameters{{ 1181 DriverName: driver1, 1182 Parameters: runtime.RawExtension{Raw: []byte("class-parameters")}, 1183 }} 1184 return parameters 1185 }(), 1186 1187 claimParameters: func() *resourceapi.ResourceClaimParameters { 1188 parameters := claimParametersOne.DeepCopy() 1189 parameters.DriverRequests[0].VendorParameters = runtime.RawExtension{Raw: []byte("claim-parameters")} 1190 parameters.DriverRequests[0].Requests[0].VendorParameters = runtime.RawExtension{Raw: []byte("request-parameters")} 1191 return parameters 1192 }(), 1193 1194 expectNodeResults: nodeResults{ 1195 node1: {isSuitable: true, driverName: driver1, 1196 allocation: func() *resourceapi.AllocationResult { 1197 allocation := instance1Allocation.DeepCopy() 1198 allocation.ResourceHandles[0].StructuredData.VendorClassParameters = runtime.RawExtension{Raw: []byte("class-parameters")} 1199 allocation.ResourceHandles[0].StructuredData.VendorClaimParameters = runtime.RawExtension{Raw: []byte("claim-parameters")} 1200 allocation.ResourceHandles[0].StructuredData.Results[0].VendorRequestParameters = runtime.RawExtension{Raw: []byte("request-parameters")} 1201 return allocation 1202 }(), 1203 }, 1204 }, 1205 }, 1206 } 1207 1208 for name, tc := range testcases { 1209 t.Run(name, func(t *testing.T) { 1210 tCtx := ktesting.Init(t) 1211 1212 controller, err := newClaimController(tCtx.Logger(), tc.class, tc.classParameters, tc.claimParameters) 1213 if err != nil { 1214 if !tc.expectCreateErr { 1215 tCtx.Fatalf("unexpected error: %v", err) 1216 } 1217 return 1218 } 1219 if tc.expectCreateErr { 1220 tCtx.Fatalf("did not get expected error") 1221 } 1222 1223 for nodeName, expect := range tc.expectNodeResults { 1224 t.Run(nodeName, func(t *testing.T) { 1225 tCtx := ktesting.Init(t) 1226 1227 isSuitable, err := controller.nodeIsSuitable(tCtx, nodeName, tc.resources) 1228 if err != nil { 1229 if expect.suitableErr == "" { 1230 tCtx.Fatalf("unexpected nodeIsSuitable error: %v", err) 1231 } 1232 require.Equal(tCtx, expect.suitableErr, err.Error()) 1233 return 1234 } 1235 if expect.suitableErr != "" { 1236 tCtx.Fatalf("did not get expected nodeIsSuitable error: %v", expect.suitableErr) 1237 } 1238 assert.Equal(tCtx, expect.isSuitable, isSuitable, "is suitable") 1239 1240 driverName, allocation, err := controller.allocate(tCtx, nodeName, tc.resources) 1241 if err != nil { 1242 if expect.allocateErr == "" { 1243 tCtx.Fatalf("unexpected allocate error: %v", err) 1244 } 1245 require.Equal(tCtx, expect.allocateErr, err.Error()) 1246 return 1247 } 1248 if expect.allocateErr != "" { 1249 tCtx.Fatalf("did not get expected allocate error: %v", expect.allocateErr) 1250 } 1251 assert.Equal(tCtx, expect.driverName, driverName, "driver name") 1252 assert.Equal(tCtx, expect.allocation, allocation) 1253 }) 1254 } 1255 }) 1256 } 1257 }