k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/cm/dra/manager_test.go (about) 1 /* 2 Copyright 2023 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 dra 18 19 import ( 20 "context" 21 "fmt" 22 "net" 23 "os" 24 "path/filepath" 25 "sync" 26 "sync/atomic" 27 "testing" 28 "time" 29 30 "github.com/stretchr/testify/assert" 31 "google.golang.org/grpc" 32 v1 "k8s.io/api/core/v1" 33 resourcev1alpha2 "k8s.io/api/resource/v1alpha2" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/types" 36 "k8s.io/apimachinery/pkg/util/sets" 37 "k8s.io/client-go/kubernetes/fake" 38 "k8s.io/dynamic-resource-allocation/resourceclaim" 39 drapbv1 "k8s.io/kubelet/pkg/apis/dra/v1alpha3" 40 "k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin" 41 "k8s.io/kubernetes/pkg/kubelet/cm/dra/state" 42 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 43 ) 44 45 const ( 46 driverName = "test-cdi-device" 47 driverClassName = "test" 48 ) 49 50 type fakeDRADriverGRPCServer struct { 51 drapbv1.UnimplementedNodeServer 52 driverName string 53 timeout *time.Duration 54 prepareResourceCalls atomic.Uint32 55 unprepareResourceCalls atomic.Uint32 56 prepareResourcesResponse *drapbv1.NodePrepareResourcesResponse 57 unprepareResourcesResponse *drapbv1.NodeUnprepareResourcesResponse 58 } 59 60 func (s *fakeDRADriverGRPCServer) NodePrepareResources(ctx context.Context, req *drapbv1.NodePrepareResourcesRequest) (*drapbv1.NodePrepareResourcesResponse, error) { 61 s.prepareResourceCalls.Add(1) 62 63 if s.timeout != nil { 64 time.Sleep(*s.timeout) 65 } 66 67 if s.prepareResourcesResponse == nil { 68 deviceName := "claim-" + req.Claims[0].Uid 69 result := s.driverName + "/" + driverClassName + "=" + deviceName 70 return &drapbv1.NodePrepareResourcesResponse{ 71 Claims: map[string]*drapbv1.NodePrepareResourceResponse{ 72 req.Claims[0].Uid: { 73 CDIDevices: []string{result}, 74 }, 75 }, 76 }, nil 77 } 78 79 return s.prepareResourcesResponse, nil 80 } 81 82 func (s *fakeDRADriverGRPCServer) NodeUnprepareResources(ctx context.Context, req *drapbv1.NodeUnprepareResourcesRequest) (*drapbv1.NodeUnprepareResourcesResponse, error) { 83 s.unprepareResourceCalls.Add(1) 84 85 if s.timeout != nil { 86 time.Sleep(*s.timeout) 87 } 88 89 if s.unprepareResourcesResponse == nil { 90 return &drapbv1.NodeUnprepareResourcesResponse{ 91 Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{ 92 req.Claims[0].Uid: {}, 93 }, 94 }, nil 95 } 96 97 return s.unprepareResourcesResponse, nil 98 } 99 100 type tearDown func() 101 102 type fakeDRAServerInfo struct { 103 // fake DRA server 104 server *fakeDRADriverGRPCServer 105 // fake DRA plugin socket name 106 socketName string 107 // teardownFn stops fake gRPC server 108 teardownFn tearDown 109 } 110 111 func setupFakeDRADriverGRPCServer(shouldTimeout bool, pluginClientTimeout *time.Duration, prepareResourcesResponse *drapbv1.NodePrepareResourcesResponse, unprepareResourcesResponse *drapbv1.NodeUnprepareResourcesResponse) (fakeDRAServerInfo, error) { 112 socketDir, err := os.MkdirTemp("", "dra") 113 if err != nil { 114 return fakeDRAServerInfo{ 115 server: nil, 116 socketName: "", 117 teardownFn: nil, 118 }, err 119 } 120 121 socketName := filepath.Join(socketDir, "server.sock") 122 stopCh := make(chan struct{}) 123 124 teardown := func() { 125 close(stopCh) 126 os.RemoveAll(socketName) 127 } 128 129 l, err := net.Listen("unix", socketName) 130 if err != nil { 131 teardown() 132 return fakeDRAServerInfo{ 133 server: nil, 134 socketName: "", 135 teardownFn: nil, 136 }, err 137 } 138 139 s := grpc.NewServer() 140 fakeDRADriverGRPCServer := &fakeDRADriverGRPCServer{ 141 driverName: driverName, 142 prepareResourcesResponse: prepareResourcesResponse, 143 unprepareResourcesResponse: unprepareResourcesResponse, 144 } 145 if shouldTimeout { 146 timeout := *pluginClientTimeout * 2 147 fakeDRADriverGRPCServer.timeout = &timeout 148 } 149 150 drapbv1.RegisterNodeServer(s, fakeDRADriverGRPCServer) 151 152 go func() { 153 go s.Serve(l) 154 <-stopCh 155 s.GracefulStop() 156 }() 157 158 return fakeDRAServerInfo{ 159 server: fakeDRADriverGRPCServer, 160 socketName: socketName, 161 teardownFn: teardown, 162 }, nil 163 } 164 165 func TestNewManagerImpl(t *testing.T) { 166 kubeClient := fake.NewSimpleClientset() 167 for _, test := range []struct { 168 description string 169 stateFileDirectory string 170 wantErr bool 171 }{ 172 { 173 description: "invalid directory path", 174 stateFileDirectory: "", 175 wantErr: true, 176 }, 177 { 178 description: "valid directory path", 179 stateFileDirectory: t.TempDir(), 180 }, 181 } { 182 t.Run(test.description, func(t *testing.T) { 183 manager, err := NewManagerImpl(kubeClient, test.stateFileDirectory, "worker") 184 if test.wantErr { 185 assert.Error(t, err) 186 return 187 } 188 189 assert.NoError(t, err) 190 assert.NotNil(t, manager.cache) 191 assert.NotNil(t, manager.kubeClient) 192 }) 193 } 194 } 195 196 func TestGetResources(t *testing.T) { 197 kubeClient := fake.NewSimpleClientset() 198 resourceClaimName := "test-pod-claim-1" 199 200 for _, test := range []struct { 201 description string 202 container *v1.Container 203 pod *v1.Pod 204 claimInfo *ClaimInfo 205 wantErr bool 206 }{ 207 { 208 description: "claim info with annotations", 209 container: &v1.Container{ 210 Name: "test-container", 211 Resources: v1.ResourceRequirements{ 212 Claims: []v1.ResourceClaim{ 213 { 214 Name: "test-pod-claim-1", 215 }, 216 }, 217 }, 218 }, 219 pod: &v1.Pod{ 220 ObjectMeta: metav1.ObjectMeta{ 221 Name: "test-pod", 222 Namespace: "test-namespace", 223 }, 224 Spec: v1.PodSpec{ 225 ResourceClaims: []v1.PodResourceClaim{ 226 { 227 Name: "test-pod-claim-1", 228 Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName}, 229 }, 230 }, 231 }, 232 }, 233 claimInfo: &ClaimInfo{ 234 annotations: map[string][]kubecontainer.Annotation{ 235 "test-plugin": { 236 { 237 Name: "test-annotation", 238 Value: "123", 239 }, 240 }, 241 }, 242 ClaimInfoState: state.ClaimInfoState{ 243 ClaimName: "test-pod-claim-1", 244 CDIDevices: map[string][]string{ 245 driverName: {"123"}, 246 }, 247 Namespace: "test-namespace", 248 }, 249 }, 250 }, 251 { 252 description: "claim info without annotations", 253 container: &v1.Container{ 254 Name: "test-container", 255 Resources: v1.ResourceRequirements{ 256 Claims: []v1.ResourceClaim{ 257 { 258 Name: "test-pod-claim-1", 259 }, 260 }, 261 }, 262 }, 263 pod: &v1.Pod{ 264 ObjectMeta: metav1.ObjectMeta{ 265 Name: "test-pod", 266 Namespace: "test-namespace", 267 }, 268 Spec: v1.PodSpec{ 269 ResourceClaims: []v1.PodResourceClaim{ 270 { 271 Name: "test-pod-claim-1", 272 Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName}, 273 }, 274 }, 275 }, 276 }, 277 claimInfo: &ClaimInfo{ 278 ClaimInfoState: state.ClaimInfoState{ 279 ClaimName: "test-pod-claim-1", 280 CDIDevices: map[string][]string{ 281 driverName: {"123"}, 282 }, 283 Namespace: "test-namespace", 284 }, 285 }, 286 }, 287 { 288 description: "no claim info", 289 container: &v1.Container{ 290 Name: "test-container", 291 Resources: v1.ResourceRequirements{ 292 Claims: []v1.ResourceClaim{ 293 { 294 Name: "test-pod-claim-1", 295 }, 296 }, 297 }, 298 }, 299 pod: &v1.Pod{ 300 ObjectMeta: metav1.ObjectMeta{ 301 Name: "test-pod", 302 Namespace: "test-namespace", 303 }, 304 Spec: v1.PodSpec{ 305 ResourceClaims: []v1.PodResourceClaim{ 306 { 307 Name: "test-pod-claim-1", 308 }, 309 }, 310 }, 311 }, 312 wantErr: true, 313 }, 314 } { 315 t.Run(test.description, func(t *testing.T) { 316 manager, err := NewManagerImpl(kubeClient, t.TempDir(), "worker") 317 assert.NoError(t, err) 318 319 if test.claimInfo != nil { 320 manager.cache.add(test.claimInfo) 321 } 322 323 containerInfo, err := manager.GetResources(test.pod, test.container) 324 if test.wantErr { 325 assert.Error(t, err) 326 return 327 } 328 329 assert.NoError(t, err) 330 assert.Equal(t, test.claimInfo.CDIDevices[driverName][0], containerInfo.CDIDevices[0].Name) 331 }) 332 } 333 } 334 335 func getFakeNode() (*v1.Node, error) { 336 return &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "worker"}}, nil 337 } 338 339 func TestPrepareResources(t *testing.T) { 340 fakeKubeClient := fake.NewSimpleClientset() 341 342 for _, test := range []struct { 343 description string 344 driverName string 345 pod *v1.Pod 346 claimInfo *ClaimInfo 347 resourceClaim *resourcev1alpha2.ResourceClaim 348 resp *drapbv1.NodePrepareResourcesResponse 349 wantErr bool 350 wantTimeout bool 351 wantResourceSkipped bool 352 expectedCDIDevices []string 353 ExpectedPrepareCalls uint32 354 }{ 355 { 356 description: "failed to fetch ResourceClaim", 357 driverName: driverName, 358 pod: &v1.Pod{ 359 ObjectMeta: metav1.ObjectMeta{ 360 Name: "test-pod", 361 Namespace: "test-namespace", 362 UID: "test-reserved", 363 }, 364 Spec: v1.PodSpec{ 365 ResourceClaims: []v1.PodResourceClaim{ 366 { 367 Name: "test-pod-claim-0", 368 Source: v1.ClaimSource{ 369 ResourceClaimName: func() *string { 370 s := "test-pod-claim-0" 371 return &s 372 }(), 373 }, 374 }, 375 }, 376 }, 377 }, 378 wantErr: true, 379 }, 380 { 381 description: "plugin does not exist", 382 driverName: "this-plugin-does-not-exist", 383 pod: &v1.Pod{ 384 ObjectMeta: metav1.ObjectMeta{ 385 Name: "test-pod", 386 Namespace: "test-namespace", 387 UID: "test-reserved", 388 }, 389 Spec: v1.PodSpec{ 390 ResourceClaims: []v1.PodResourceClaim{ 391 { 392 Name: "test-pod-claim-1", 393 Source: v1.ClaimSource{ 394 ResourceClaimName: func() *string { 395 s := "test-pod-claim-1" 396 return &s 397 }(), 398 }, 399 }, 400 }, 401 Containers: []v1.Container{ 402 { 403 Resources: v1.ResourceRequirements{ 404 Claims: []v1.ResourceClaim{ 405 { 406 Name: "test-pod-claim-1", 407 }, 408 }, 409 }, 410 }, 411 }, 412 }, 413 }, 414 resourceClaim: &resourcev1alpha2.ResourceClaim{ 415 ObjectMeta: metav1.ObjectMeta{ 416 Name: "test-pod-claim-1", 417 Namespace: "test-namespace", 418 UID: "test-reserved", 419 }, 420 Spec: resourcev1alpha2.ResourceClaimSpec{ 421 ResourceClassName: "test-class", 422 }, 423 Status: resourcev1alpha2.ResourceClaimStatus{ 424 DriverName: driverName, 425 Allocation: &resourcev1alpha2.AllocationResult{ 426 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 427 {Data: "test-data", DriverName: driverName}, 428 }, 429 }, 430 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 431 {UID: "test-reserved"}, 432 }, 433 }, 434 }, 435 wantErr: true, 436 }, 437 { 438 description: "should prepare resources, driver returns nil value", 439 driverName: driverName, 440 pod: &v1.Pod{ 441 ObjectMeta: metav1.ObjectMeta{ 442 Name: "test-pod", 443 Namespace: "test-namespace", 444 UID: "test-reserved", 445 }, 446 Spec: v1.PodSpec{ 447 ResourceClaims: []v1.PodResourceClaim{ 448 { 449 Name: "test-pod-claim-nil", 450 Source: v1.ClaimSource{ 451 ResourceClaimName: func() *string { 452 s := "test-pod-claim-nil" 453 return &s 454 }(), 455 }, 456 }, 457 }, 458 Containers: []v1.Container{ 459 { 460 Resources: v1.ResourceRequirements{ 461 Claims: []v1.ResourceClaim{ 462 { 463 Name: "test-pod-claim-nil", 464 }, 465 }, 466 }, 467 }, 468 }, 469 }, 470 }, 471 resourceClaim: &resourcev1alpha2.ResourceClaim{ 472 ObjectMeta: metav1.ObjectMeta{ 473 Name: "test-pod-claim-nil", 474 Namespace: "test-namespace", 475 UID: "test-reserved", 476 }, 477 Spec: resourcev1alpha2.ResourceClaimSpec{ 478 ResourceClassName: "test-class", 479 }, 480 Status: resourcev1alpha2.ResourceClaimStatus{ 481 DriverName: driverName, 482 Allocation: &resourcev1alpha2.AllocationResult{ 483 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 484 {Data: "test-data", DriverName: driverName}, 485 }, 486 }, 487 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 488 {UID: "test-reserved"}, 489 }, 490 }, 491 }, 492 resp: &drapbv1.NodePrepareResourcesResponse{Claims: map[string]*drapbv1.NodePrepareResourceResponse{"test-reserved": nil}}, 493 expectedCDIDevices: []string{}, 494 ExpectedPrepareCalls: 1, 495 }, 496 { 497 description: "should prepare resources, driver returns empty result", 498 driverName: driverName, 499 pod: &v1.Pod{ 500 ObjectMeta: metav1.ObjectMeta{ 501 Name: "test-pod", 502 Namespace: "test-namespace", 503 UID: "test-reserved", 504 }, 505 Spec: v1.PodSpec{ 506 ResourceClaims: []v1.PodResourceClaim{ 507 { 508 Name: "test-pod-claim-empty", 509 Source: v1.ClaimSource{ 510 ResourceClaimName: func() *string { 511 s := "test-pod-claim-empty" 512 return &s 513 }(), 514 }, 515 }, 516 }, 517 Containers: []v1.Container{ 518 { 519 Resources: v1.ResourceRequirements{ 520 Claims: []v1.ResourceClaim{ 521 { 522 Name: "test-pod-claim-empty", 523 }, 524 }, 525 }, 526 }, 527 }, 528 }, 529 }, 530 resourceClaim: &resourcev1alpha2.ResourceClaim{ 531 ObjectMeta: metav1.ObjectMeta{ 532 Name: "test-pod-claim-empty", 533 Namespace: "test-namespace", 534 UID: "test-reserved", 535 }, 536 Spec: resourcev1alpha2.ResourceClaimSpec{ 537 ResourceClassName: "test-class", 538 }, 539 Status: resourcev1alpha2.ResourceClaimStatus{ 540 DriverName: driverName, 541 Allocation: &resourcev1alpha2.AllocationResult{ 542 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 543 {Data: "test-data", DriverName: driverName}, 544 }, 545 }, 546 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 547 {UID: "test-reserved"}, 548 }, 549 }, 550 }, 551 resp: &drapbv1.NodePrepareResourcesResponse{Claims: map[string]*drapbv1.NodePrepareResourceResponse{"test-reserved": nil}}, 552 expectedCDIDevices: []string{}, 553 ExpectedPrepareCalls: 1, 554 }, 555 { 556 description: "pod is not allowed to use resource claim", 557 driverName: driverName, 558 pod: &v1.Pod{ 559 ObjectMeta: metav1.ObjectMeta{ 560 Name: "test-pod", 561 Namespace: "test-namespace", 562 UID: "test-reserved", 563 }, 564 Spec: v1.PodSpec{ 565 ResourceClaims: []v1.PodResourceClaim{ 566 { 567 Name: "test-pod-claim-2", 568 Source: v1.ClaimSource{ 569 ResourceClaimName: func() *string { 570 s := "test-pod-claim-2" 571 return &s 572 }(), 573 }, 574 }, 575 }, 576 }, 577 }, 578 resourceClaim: &resourcev1alpha2.ResourceClaim{ 579 ObjectMeta: metav1.ObjectMeta{ 580 Name: "test-pod-claim-2", 581 Namespace: "test-namespace", 582 UID: "test-reserved", 583 }, 584 Spec: resourcev1alpha2.ResourceClaimSpec{ 585 ResourceClassName: "test-class", 586 }, 587 Status: resourcev1alpha2.ResourceClaimStatus{ 588 DriverName: driverName, 589 Allocation: &resourcev1alpha2.AllocationResult{ 590 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 591 {Data: "test-data", DriverName: driverName}, 592 }, 593 }, 594 }, 595 }, 596 wantErr: true, 597 }, 598 { 599 description: "no container actually uses the claim", 600 driverName: driverName, 601 pod: &v1.Pod{ 602 ObjectMeta: metav1.ObjectMeta{ 603 Name: "test-pod", 604 Namespace: "test-namespace", 605 UID: "test-reserved", 606 }, 607 Spec: v1.PodSpec{ 608 ResourceClaims: []v1.PodResourceClaim{ 609 { 610 Name: "test-pod-claim-3", 611 Source: v1.ClaimSource{ResourceClaimName: func() *string { 612 s := "test-pod-claim-3" 613 return &s 614 }()}, 615 }, 616 }, 617 }, 618 }, 619 resourceClaim: &resourcev1alpha2.ResourceClaim{ 620 ObjectMeta: metav1.ObjectMeta{ 621 Name: "test-pod-claim-3", 622 Namespace: "test-namespace", 623 UID: "test-reserved", 624 }, 625 Spec: resourcev1alpha2.ResourceClaimSpec{ 626 ResourceClassName: "test-class", 627 }, 628 Status: resourcev1alpha2.ResourceClaimStatus{ 629 DriverName: driverName, 630 Allocation: &resourcev1alpha2.AllocationResult{ 631 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 632 {Data: "test-data", DriverName: driverName}, 633 }, 634 }, 635 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 636 {UID: "test-reserved"}, 637 }, 638 }, 639 }, 640 wantResourceSkipped: true, 641 }, 642 { 643 description: "resource already prepared", 644 driverName: driverName, 645 pod: &v1.Pod{ 646 ObjectMeta: metav1.ObjectMeta{ 647 Name: "test-pod", 648 Namespace: "test-namespace", 649 UID: "test-reserved", 650 }, 651 Spec: v1.PodSpec{ 652 ResourceClaims: []v1.PodResourceClaim{ 653 { 654 Name: "test-pod-claim-4", 655 Source: v1.ClaimSource{ResourceClaimName: func() *string { 656 s := "test-pod-claim-4" 657 return &s 658 }()}, 659 }, 660 }, 661 Containers: []v1.Container{ 662 { 663 Resources: v1.ResourceRequirements{ 664 Claims: []v1.ResourceClaim{ 665 { 666 Name: "test-pod-claim-4", 667 }, 668 }, 669 }, 670 }, 671 }, 672 }, 673 }, 674 claimInfo: &ClaimInfo{ 675 ClaimInfoState: state.ClaimInfoState{ 676 DriverName: driverName, 677 ClaimName: "test-pod-claim-4", 678 Namespace: "test-namespace", 679 PodUIDs: sets.Set[string]{"test-another-pod-reserved": sets.Empty{}}, 680 }, 681 prepared: true, 682 }, 683 resourceClaim: &resourcev1alpha2.ResourceClaim{ 684 ObjectMeta: metav1.ObjectMeta{ 685 Name: "test-pod-claim-4", 686 Namespace: "test-namespace", 687 UID: "test-reserved", 688 }, 689 Spec: resourcev1alpha2.ResourceClaimSpec{ 690 ResourceClassName: "test-class", 691 }, 692 Status: resourcev1alpha2.ResourceClaimStatus{ 693 DriverName: driverName, 694 Allocation: &resourcev1alpha2.AllocationResult{ 695 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 696 {Data: "test-data", DriverName: driverName}, 697 }, 698 }, 699 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 700 {UID: "test-reserved"}, 701 }, 702 }, 703 }, 704 expectedCDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}, 705 wantResourceSkipped: true, 706 }, 707 { 708 description: "should timeout", 709 driverName: driverName, 710 pod: &v1.Pod{ 711 ObjectMeta: metav1.ObjectMeta{ 712 Name: "test-pod", 713 Namespace: "test-namespace", 714 UID: "test-reserved", 715 }, 716 Spec: v1.PodSpec{ 717 ResourceClaims: []v1.PodResourceClaim{ 718 { 719 Name: "test-pod-claim-5", 720 Source: v1.ClaimSource{ResourceClaimName: func() *string { 721 s := "test-pod-claim-5" 722 return &s 723 }()}, 724 }, 725 }, 726 Containers: []v1.Container{ 727 { 728 Resources: v1.ResourceRequirements{ 729 Claims: []v1.ResourceClaim{ 730 { 731 Name: "test-pod-claim-5", 732 }, 733 }, 734 }, 735 }, 736 }, 737 }, 738 }, 739 resourceClaim: &resourcev1alpha2.ResourceClaim{ 740 ObjectMeta: metav1.ObjectMeta{ 741 Name: "test-pod-claim-5", 742 Namespace: "test-namespace", 743 UID: "test-reserved", 744 }, 745 Spec: resourcev1alpha2.ResourceClaimSpec{ 746 ResourceClassName: "test-class", 747 }, 748 Status: resourcev1alpha2.ResourceClaimStatus{ 749 DriverName: driverName, 750 Allocation: &resourcev1alpha2.AllocationResult{ 751 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 752 {Data: "test-data", DriverName: driverName}, 753 }, 754 }, 755 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 756 {UID: "test-reserved"}, 757 }, 758 }, 759 }, 760 resp: &drapbv1.NodePrepareResourcesResponse{ 761 Claims: map[string]*drapbv1.NodePrepareResourceResponse{ 762 "test-reserved": {CDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}}, 763 }, 764 }, 765 wantErr: true, 766 wantTimeout: true, 767 ExpectedPrepareCalls: 1, 768 }, 769 { 770 description: "should prepare resource, claim not in cache", 771 driverName: driverName, 772 pod: &v1.Pod{ 773 ObjectMeta: metav1.ObjectMeta{ 774 Name: "test-pod", 775 Namespace: "test-namespace", 776 UID: "test-reserved", 777 }, 778 Spec: v1.PodSpec{ 779 ResourceClaims: []v1.PodResourceClaim{ 780 { 781 Name: "test-pod-claim-6", 782 Source: v1.ClaimSource{ResourceClaimName: func() *string { 783 s := "test-pod-claim-6" 784 return &s 785 }()}, 786 }, 787 }, 788 Containers: []v1.Container{ 789 { 790 Resources: v1.ResourceRequirements{ 791 Claims: []v1.ResourceClaim{ 792 { 793 Name: "test-pod-claim-6", 794 }, 795 }, 796 }, 797 }, 798 }, 799 }, 800 }, 801 resourceClaim: &resourcev1alpha2.ResourceClaim{ 802 ObjectMeta: metav1.ObjectMeta{ 803 Name: "test-pod-claim-6", 804 Namespace: "test-namespace", 805 UID: "test-reserved", 806 }, 807 Spec: resourcev1alpha2.ResourceClaimSpec{ 808 ResourceClassName: "test-class", 809 }, 810 Status: resourcev1alpha2.ResourceClaimStatus{ 811 DriverName: driverName, 812 Allocation: &resourcev1alpha2.AllocationResult{ 813 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 814 {Data: "test-data", DriverName: driverName}, 815 }, 816 }, 817 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 818 {UID: "test-reserved"}, 819 }, 820 }, 821 }, 822 resp: &drapbv1.NodePrepareResourcesResponse{ 823 Claims: map[string]*drapbv1.NodePrepareResourceResponse{ 824 "test-reserved": {CDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}}, 825 }, 826 }, 827 expectedCDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}, 828 ExpectedPrepareCalls: 1, 829 }, 830 { 831 description: "should prepare resource. claim in cache, manager did not prepare resource", 832 driverName: driverName, 833 pod: &v1.Pod{ 834 ObjectMeta: metav1.ObjectMeta{ 835 Name: "test-pod", 836 Namespace: "test-namespace", 837 UID: "test-reserved", 838 }, 839 Spec: v1.PodSpec{ 840 ResourceClaims: []v1.PodResourceClaim{ 841 { 842 Name: "test-pod-claim", 843 Source: v1.ClaimSource{ResourceClaimName: func() *string { 844 s := "test-pod-claim" 845 return &s 846 }()}, 847 }, 848 }, 849 Containers: []v1.Container{ 850 { 851 Resources: v1.ResourceRequirements{ 852 Claims: []v1.ResourceClaim{ 853 { 854 Name: "test-pod-claim", 855 }, 856 }, 857 }, 858 }, 859 }, 860 }, 861 }, 862 claimInfo: &ClaimInfo{ 863 ClaimInfoState: state.ClaimInfoState{ 864 DriverName: driverName, 865 ClassName: "test-class", 866 ClaimName: "test-pod-claim", 867 ClaimUID: "test-reserved", 868 Namespace: "test-namespace", 869 PodUIDs: sets.Set[string]{"test-reserved": sets.Empty{}}, 870 ResourceHandles: []resourcev1alpha2.ResourceHandle{{Data: "test-data", DriverName: driverName}}, 871 }, 872 annotations: make(map[string][]kubecontainer.Annotation), 873 prepared: false, 874 }, 875 resourceClaim: &resourcev1alpha2.ResourceClaim{ 876 ObjectMeta: metav1.ObjectMeta{ 877 Name: "test-pod-claim", 878 Namespace: "test-namespace", 879 UID: "test-reserved", 880 }, 881 Spec: resourcev1alpha2.ResourceClaimSpec{ 882 ResourceClassName: "test-class", 883 }, 884 Status: resourcev1alpha2.ResourceClaimStatus{ 885 DriverName: driverName, 886 Allocation: &resourcev1alpha2.AllocationResult{ 887 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 888 {Data: "test-data", DriverName: driverName}, 889 }, 890 }, 891 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 892 {UID: "test-reserved"}, 893 }, 894 }, 895 }, 896 resp: &drapbv1.NodePrepareResourcesResponse{ 897 Claims: map[string]*drapbv1.NodePrepareResourceResponse{ 898 "test-reserved": {CDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}}, 899 }, 900 }, 901 expectedCDIDevices: []string{fmt.Sprintf("%s/%s=claim-test-reserved", driverName, driverClassName)}, 902 ExpectedPrepareCalls: 1, 903 }, 904 } { 905 t.Run(test.description, func(t *testing.T) { 906 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 907 if err != nil { 908 t.Fatalf("failed to newClaimInfoCache, err:%v", err) 909 } 910 911 manager := &ManagerImpl{ 912 kubeClient: fakeKubeClient, 913 cache: cache, 914 } 915 916 if test.resourceClaim != nil { 917 if _, err := fakeKubeClient.ResourceV1alpha2().ResourceClaims(test.pod.Namespace).Create(context.Background(), test.resourceClaim, metav1.CreateOptions{}); err != nil { 918 t.Fatalf("failed to create ResourceClaim %s: %+v", test.resourceClaim.Name, err) 919 } 920 } 921 922 var pluginClientTimeout *time.Duration 923 if test.wantTimeout { 924 timeout := time.Millisecond * 20 925 pluginClientTimeout = &timeout 926 } 927 928 draServerInfo, err := setupFakeDRADriverGRPCServer(test.wantTimeout, pluginClientTimeout, test.resp, nil) 929 if err != nil { 930 t.Fatal(err) 931 } 932 defer draServerInfo.teardownFn() 933 934 plg := plugin.NewRegistrationHandler(nil, getFakeNode) 935 if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{"1.27"}, pluginClientTimeout); err != nil { 936 t.Fatalf("failed to register plugin %s, err: %v", test.driverName, err) 937 } 938 defer plg.DeRegisterPlugin(test.driverName) // for sake of next tests 939 940 if test.claimInfo != nil { 941 manager.cache.add(test.claimInfo) 942 } 943 944 err = manager.PrepareResources(test.pod) 945 946 assert.Equal(t, test.ExpectedPrepareCalls, draServerInfo.server.prepareResourceCalls.Load()) 947 948 if test.wantErr { 949 assert.Error(t, err) 950 return // PrepareResources returned an error so stopping the subtest here 951 } else if test.wantResourceSkipped { 952 assert.NoError(t, err) 953 return // resource skipped so no need to continue 954 } 955 956 assert.NoError(t, err) 957 // check the cache contains the expected claim info 958 claimName, _, err := resourceclaim.Name(test.pod, &test.pod.Spec.ResourceClaims[0]) 959 if err != nil { 960 t.Fatal(err) 961 } 962 claimInfo, ok := manager.cache.get(*claimName, test.pod.Namespace) 963 if !ok { 964 t.Fatalf("claimInfo not found in cache for claim %s", *claimName) 965 } 966 if claimInfo.DriverName != test.resourceClaim.Status.DriverName { 967 t.Fatalf("driverName mismatch: expected %s, got %s", test.resourceClaim.Status.DriverName, claimInfo.DriverName) 968 } 969 if claimInfo.ClassName != test.resourceClaim.Spec.ResourceClassName { 970 t.Fatalf("resourceClassName mismatch: expected %s, got %s", test.resourceClaim.Spec.ResourceClassName, claimInfo.ClassName) 971 } 972 if len(claimInfo.PodUIDs) != 1 || !claimInfo.PodUIDs.Has(string(test.pod.UID)) { 973 t.Fatalf("podUIDs mismatch: expected [%s], got %v", test.pod.UID, claimInfo.PodUIDs) 974 } 975 assert.ElementsMatchf(t, claimInfo.CDIDevices[test.resourceClaim.Status.DriverName], test.expectedCDIDevices, 976 "cdiDevices mismatch: expected [%v], got %v", test.expectedCDIDevices, claimInfo.CDIDevices[test.resourceClaim.Status.DriverName]) 977 }) 978 } 979 } 980 981 func TestUnprepareResources(t *testing.T) { 982 fakeKubeClient := fake.NewSimpleClientset() 983 984 for _, test := range []struct { 985 description string 986 driverName string 987 pod *v1.Pod 988 claimInfo *ClaimInfo 989 resp *drapbv1.NodeUnprepareResourcesResponse 990 wantErr bool 991 wantTimeout bool 992 wantResourceSkipped bool 993 expectedUnprepareCalls uint32 994 }{ 995 { 996 description: "plugin does not exist", 997 driverName: "this-plugin-does-not-exist", 998 pod: &v1.Pod{ 999 ObjectMeta: metav1.ObjectMeta{ 1000 Name: "test-pod", 1001 Namespace: "test-namespace", 1002 UID: "test-reserved", 1003 }, 1004 Spec: v1.PodSpec{ 1005 ResourceClaims: []v1.PodResourceClaim{ 1006 { 1007 Name: "another-claim-test", 1008 Source: v1.ClaimSource{ 1009 ResourceClaimName: func() *string { 1010 s := "another-claim-test" 1011 return &s 1012 }(), 1013 }, 1014 }, 1015 }, 1016 Containers: []v1.Container{ 1017 { 1018 Resources: v1.ResourceRequirements{ 1019 Claims: []v1.ResourceClaim{ 1020 { 1021 Name: "another-claim-test", 1022 }, 1023 }, 1024 }, 1025 }, 1026 }, 1027 }, 1028 }, 1029 claimInfo: &ClaimInfo{ 1030 ClaimInfoState: state.ClaimInfoState{ 1031 DriverName: driverName, 1032 ClaimName: "another-claim-test", 1033 Namespace: "test-namespace", 1034 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 1035 { 1036 DriverName: driverName, 1037 Data: "test data", 1038 }, 1039 }, 1040 }, 1041 }, 1042 wantErr: true, 1043 }, 1044 { 1045 description: "resource claim referenced by other pod(s)", 1046 driverName: driverName, 1047 pod: &v1.Pod{ 1048 ObjectMeta: metav1.ObjectMeta{ 1049 Name: "test-pod", 1050 Namespace: "test-namespace", 1051 UID: "test-reserved", 1052 }, 1053 Spec: v1.PodSpec{ 1054 ResourceClaims: []v1.PodResourceClaim{ 1055 { 1056 Name: "test-pod-claim-1", 1057 Source: v1.ClaimSource{ResourceClaimName: func() *string { 1058 s := "test-pod-claim-1" 1059 return &s 1060 }()}, 1061 }, 1062 }, 1063 Containers: []v1.Container{ 1064 { 1065 Resources: v1.ResourceRequirements{ 1066 Claims: []v1.ResourceClaim{ 1067 { 1068 Name: "test-pod-claim-1", 1069 }, 1070 }, 1071 }, 1072 }, 1073 }, 1074 }, 1075 }, 1076 claimInfo: &ClaimInfo{ 1077 ClaimInfoState: state.ClaimInfoState{ 1078 DriverName: driverName, 1079 ClaimName: "test-pod-claim-1", 1080 Namespace: "test-namespace", 1081 PodUIDs: sets.Set[string]{"test-reserved": sets.Empty{}, "test-reserved-2": sets.Empty{}}, 1082 }, 1083 }, 1084 wantResourceSkipped: true, 1085 }, 1086 { 1087 description: "should timeout", 1088 driverName: driverName, 1089 pod: &v1.Pod{ 1090 ObjectMeta: metav1.ObjectMeta{ 1091 Name: "test-pod", 1092 Namespace: "test-namespace", 1093 UID: "test-reserved", 1094 }, 1095 Spec: v1.PodSpec{ 1096 ResourceClaims: []v1.PodResourceClaim{ 1097 { 1098 Name: "test-pod-claim-2", 1099 Source: v1.ClaimSource{ResourceClaimName: func() *string { 1100 s := "test-pod-claim-2" 1101 return &s 1102 }()}, 1103 }, 1104 }, 1105 Containers: []v1.Container{ 1106 { 1107 Resources: v1.ResourceRequirements{ 1108 Claims: []v1.ResourceClaim{ 1109 { 1110 Name: "test-pod-claim-2", 1111 }, 1112 }, 1113 }, 1114 }, 1115 }, 1116 }, 1117 }, 1118 claimInfo: &ClaimInfo{ 1119 ClaimInfoState: state.ClaimInfoState{ 1120 DriverName: driverName, 1121 ClaimName: "test-pod-claim-2", 1122 Namespace: "test-namespace", 1123 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 1124 { 1125 DriverName: driverName, 1126 Data: "test data", 1127 }, 1128 }, 1129 }, 1130 }, 1131 resp: &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{"test-reserved": {}}}, 1132 wantErr: true, 1133 wantTimeout: true, 1134 expectedUnprepareCalls: 1, 1135 }, 1136 { 1137 description: "should unprepare resource, claim previously prepared by currently running manager", 1138 driverName: driverName, 1139 pod: &v1.Pod{ 1140 ObjectMeta: metav1.ObjectMeta{ 1141 Name: "test-pod", 1142 Namespace: "test-namespace", 1143 UID: "test-reserved", 1144 }, 1145 Spec: v1.PodSpec{ 1146 ResourceClaims: []v1.PodResourceClaim{ 1147 { 1148 Name: "test-pod-claim-3", 1149 Source: v1.ClaimSource{ResourceClaimName: func() *string { 1150 s := "test-pod-claim-3" 1151 return &s 1152 }()}, 1153 }, 1154 }, 1155 Containers: []v1.Container{ 1156 { 1157 Resources: v1.ResourceRequirements{ 1158 Claims: []v1.ResourceClaim{ 1159 { 1160 Name: "test-pod-claim-3", 1161 }, 1162 }, 1163 }, 1164 }, 1165 }, 1166 }, 1167 }, 1168 claimInfo: &ClaimInfo{ 1169 ClaimInfoState: state.ClaimInfoState{ 1170 DriverName: driverName, 1171 ClaimName: "test-pod-claim-3", 1172 Namespace: "test-namespace", 1173 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 1174 { 1175 DriverName: driverName, 1176 Data: "test data", 1177 }, 1178 }, 1179 }, 1180 prepared: true, 1181 }, 1182 resp: &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{"": {}}}, 1183 expectedUnprepareCalls: 1, 1184 }, 1185 { 1186 description: "should unprepare resource, claim previously was not prepared by currently running manager", 1187 driverName: driverName, 1188 pod: &v1.Pod{ 1189 ObjectMeta: metav1.ObjectMeta{ 1190 Name: "test-pod", 1191 Namespace: "test-namespace", 1192 UID: "test-reserved", 1193 }, 1194 Spec: v1.PodSpec{ 1195 ResourceClaims: []v1.PodResourceClaim{ 1196 { 1197 Name: "test-pod-claim", 1198 Source: v1.ClaimSource{ResourceClaimName: func() *string { 1199 s := "test-pod-claim" 1200 return &s 1201 }()}, 1202 }, 1203 }, 1204 Containers: []v1.Container{ 1205 { 1206 Resources: v1.ResourceRequirements{ 1207 Claims: []v1.ResourceClaim{ 1208 { 1209 Name: "test-pod-claim", 1210 }, 1211 }, 1212 }, 1213 }, 1214 }, 1215 }, 1216 }, 1217 claimInfo: &ClaimInfo{ 1218 ClaimInfoState: state.ClaimInfoState{ 1219 DriverName: driverName, 1220 ClaimName: "test-pod-claim", 1221 Namespace: "test-namespace", 1222 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 1223 { 1224 DriverName: driverName, 1225 Data: "test data", 1226 }, 1227 }, 1228 }, 1229 prepared: false, 1230 }, 1231 resp: &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{"": {}}}, 1232 expectedUnprepareCalls: 1, 1233 }, 1234 { 1235 description: "should unprepare resource, driver returns nil value", 1236 driverName: driverName, 1237 pod: &v1.Pod{ 1238 ObjectMeta: metav1.ObjectMeta{ 1239 Name: "test-pod", 1240 Namespace: "test-namespace", 1241 UID: "test-reserved", 1242 }, 1243 Spec: v1.PodSpec{ 1244 ResourceClaims: []v1.PodResourceClaim{ 1245 { 1246 Name: "test-pod-claim-nil", 1247 Source: v1.ClaimSource{ 1248 ResourceClaimName: func() *string { 1249 s := "test-pod-claim-nil" 1250 return &s 1251 }(), 1252 }, 1253 }, 1254 }, 1255 Containers: []v1.Container{ 1256 { 1257 Resources: v1.ResourceRequirements{ 1258 Claims: []v1.ResourceClaim{ 1259 { 1260 Name: "test-pod-claim-nil", 1261 }, 1262 }, 1263 }, 1264 }, 1265 }, 1266 }, 1267 }, 1268 claimInfo: &ClaimInfo{ 1269 ClaimInfoState: state.ClaimInfoState{ 1270 DriverName: driverName, 1271 ClaimName: "test-pod-claim-nil", 1272 Namespace: "test-namespace", 1273 ClaimUID: "test-reserved", 1274 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 1275 { 1276 DriverName: driverName, 1277 Data: "test data", 1278 }, 1279 }, 1280 }, 1281 prepared: true, 1282 }, 1283 resp: &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{"test-reserved": nil}}, 1284 expectedUnprepareCalls: 1, 1285 }, 1286 } { 1287 t.Run(test.description, func(t *testing.T) { 1288 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 1289 if err != nil { 1290 t.Fatalf("failed to create a new instance of the claimInfoCache, err: %v", err) 1291 } 1292 1293 var pluginClientTimeout *time.Duration 1294 if test.wantTimeout { 1295 timeout := time.Millisecond * 20 1296 pluginClientTimeout = &timeout 1297 } 1298 1299 draServerInfo, err := setupFakeDRADriverGRPCServer(test.wantTimeout, pluginClientTimeout, nil, test.resp) 1300 if err != nil { 1301 t.Fatal(err) 1302 } 1303 defer draServerInfo.teardownFn() 1304 1305 plg := plugin.NewRegistrationHandler(nil, getFakeNode) 1306 if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{"1.27"}, pluginClientTimeout); err != nil { 1307 t.Fatalf("failed to register plugin %s, err: %v", test.driverName, err) 1308 } 1309 defer plg.DeRegisterPlugin(test.driverName) // for sake of next tests 1310 1311 manager := &ManagerImpl{ 1312 kubeClient: fakeKubeClient, 1313 cache: cache, 1314 } 1315 1316 if test.claimInfo != nil { 1317 manager.cache.add(test.claimInfo) 1318 } 1319 1320 err = manager.UnprepareResources(test.pod) 1321 1322 assert.Equal(t, test.expectedUnprepareCalls, draServerInfo.server.unprepareResourceCalls.Load()) 1323 1324 if test.wantErr { 1325 assert.Error(t, err) 1326 return // UnprepareResources returned an error so stopping the subtest here 1327 } else if test.wantResourceSkipped { 1328 assert.NoError(t, err) 1329 return // resource skipped so no need to continue 1330 } 1331 1332 assert.NoError(t, err) 1333 // Check that the cache has been updated correctly 1334 claimName, _, err := resourceclaim.Name(test.pod, &test.pod.Spec.ResourceClaims[0]) 1335 if err != nil { 1336 t.Fatal(err) 1337 } 1338 if manager.cache.contains(*claimName, test.pod.Namespace) { 1339 t.Fatalf("claimInfo still found in cache after calling UnprepareResources") 1340 } 1341 }) 1342 } 1343 } 1344 1345 func TestPodMightNeedToUnprepareResources(t *testing.T) { 1346 fakeKubeClient := fake.NewSimpleClientset() 1347 1348 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 1349 if err != nil { 1350 t.Fatalf("failed to newClaimInfoCache, err:%v", err) 1351 } 1352 1353 manager := &ManagerImpl{ 1354 kubeClient: fakeKubeClient, 1355 cache: cache, 1356 } 1357 1358 claimName := "test-claim" 1359 podUID := "test-pod-uid" 1360 namespace := "test-namespace" 1361 1362 claimInfo := &ClaimInfo{ 1363 ClaimInfoState: state.ClaimInfoState{PodUIDs: sets.New(podUID), ClaimName: claimName, Namespace: namespace}, 1364 } 1365 manager.cache.add(claimInfo) 1366 if !manager.cache.contains(claimName, namespace) { 1367 t.Fatalf("failed to get claimInfo from cache for claim name %s, namespace %s: err:%v", claimName, namespace, err) 1368 } 1369 claimInfo.addPodReference(types.UID(podUID)) 1370 needsUnprepare := manager.PodMightNeedToUnprepareResources(types.UID(podUID)) 1371 assert.True(t, needsUnprepare) 1372 } 1373 1374 func TestGetContainerClaimInfos(t *testing.T) { 1375 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 1376 if err != nil { 1377 t.Fatalf("error occur:%v", err) 1378 } 1379 manager := &ManagerImpl{ 1380 cache: cache, 1381 } 1382 1383 resourceClaimName := "test-resource-claim-1" 1384 resourceClaimName2 := "test-resource-claim-2" 1385 1386 for i, test := range []struct { 1387 expectedClaimName string 1388 pod *v1.Pod 1389 container *v1.Container 1390 claimInfo *ClaimInfo 1391 }{ 1392 { 1393 expectedClaimName: resourceClaimName, 1394 pod: &v1.Pod{ 1395 Spec: v1.PodSpec{ 1396 ResourceClaims: []v1.PodResourceClaim{ 1397 { 1398 Name: "claim1", 1399 Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName}, 1400 }, 1401 }, 1402 }, 1403 }, 1404 container: &v1.Container{ 1405 Resources: v1.ResourceRequirements{ 1406 Claims: []v1.ResourceClaim{ 1407 { 1408 Name: "claim1", 1409 }, 1410 }, 1411 }, 1412 }, 1413 claimInfo: &ClaimInfo{ClaimInfoState: state.ClaimInfoState{ClaimName: resourceClaimName}}, 1414 }, 1415 { 1416 expectedClaimName: resourceClaimName2, 1417 pod: &v1.Pod{ 1418 Spec: v1.PodSpec{ 1419 ResourceClaims: []v1.PodResourceClaim{ 1420 { 1421 Name: "claim2", 1422 Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName2}, 1423 }, 1424 }, 1425 }, 1426 }, 1427 container: &v1.Container{ 1428 Resources: v1.ResourceRequirements{ 1429 Claims: []v1.ResourceClaim{ 1430 { 1431 Name: "claim2", 1432 }, 1433 }, 1434 }, 1435 }, 1436 claimInfo: &ClaimInfo{ClaimInfoState: state.ClaimInfoState{ClaimName: resourceClaimName2}}, 1437 }, 1438 } { 1439 t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { 1440 manager.cache.add(test.claimInfo) 1441 1442 fakeClaimInfos, err := manager.GetContainerClaimInfos(test.pod, test.container) 1443 assert.NoError(t, err) 1444 assert.Equal(t, 1, len(fakeClaimInfos)) 1445 assert.Equal(t, test.expectedClaimName, fakeClaimInfos[0].ClaimInfoState.ClaimName) 1446 1447 manager.cache.delete(test.pod.Spec.ResourceClaims[0].Name, "default") 1448 _, err = manager.GetContainerClaimInfos(test.pod, test.container) 1449 assert.NoError(t, err) 1450 }) 1451 } 1452 } 1453 1454 // TestParallelPrepareUnprepareResources calls PrepareResources and UnprepareResources APIs in parallel 1455 // to detect possible data races 1456 func TestParallelPrepareUnprepareResources(t *testing.T) { 1457 // Setup and register fake DRA driver 1458 draServerInfo, err := setupFakeDRADriverGRPCServer(false, nil, nil, nil) 1459 if err != nil { 1460 t.Fatal(err) 1461 } 1462 defer draServerInfo.teardownFn() 1463 1464 plg := plugin.NewRegistrationHandler(nil, getFakeNode) 1465 if err := plg.RegisterPlugin(driverName, draServerInfo.socketName, []string{"1.27"}, nil); err != nil { 1466 t.Fatalf("failed to register plugin %s, err: %v", driverName, err) 1467 } 1468 defer plg.DeRegisterPlugin(driverName) 1469 1470 // Create ClaimInfo cache 1471 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 1472 if err != nil { 1473 t.Errorf("failed to newClaimInfoCache, err: %+v", err) 1474 return 1475 } 1476 1477 // Create fake Kube client and DRA manager 1478 fakeKubeClient := fake.NewSimpleClientset() 1479 manager := &ManagerImpl{kubeClient: fakeKubeClient, cache: cache} 1480 1481 // Call PrepareResources in parallel 1482 var wgSync, wgStart sync.WaitGroup // groups to sync goroutines 1483 numGoroutines := 30 1484 wgSync.Add(numGoroutines) 1485 wgStart.Add(1) 1486 for i := 0; i < numGoroutines; i++ { 1487 go func(t *testing.T, goRoutineNum int) { 1488 defer wgSync.Done() 1489 wgStart.Wait() // Wait to start all goroutines at the same time 1490 1491 var err error 1492 nameSpace := "test-namespace-parallel" 1493 claimName := fmt.Sprintf("test-pod-claim-%d", goRoutineNum) 1494 podUID := types.UID(fmt.Sprintf("test-reserved-%d", goRoutineNum)) 1495 pod := &v1.Pod{ 1496 ObjectMeta: metav1.ObjectMeta{ 1497 Name: fmt.Sprintf("test-pod-%d", goRoutineNum), 1498 Namespace: nameSpace, 1499 UID: podUID, 1500 }, 1501 Spec: v1.PodSpec{ 1502 ResourceClaims: []v1.PodResourceClaim{ 1503 { 1504 Name: claimName, 1505 Source: v1.ClaimSource{ResourceClaimName: func() *string { 1506 s := claimName 1507 return &s 1508 }()}, 1509 }, 1510 }, 1511 Containers: []v1.Container{ 1512 { 1513 Resources: v1.ResourceRequirements{ 1514 Claims: []v1.ResourceClaim{ 1515 { 1516 Name: claimName, 1517 }, 1518 }, 1519 }, 1520 }, 1521 }, 1522 }, 1523 } 1524 resourceClaim := &resourcev1alpha2.ResourceClaim{ 1525 ObjectMeta: metav1.ObjectMeta{ 1526 Name: claimName, 1527 Namespace: nameSpace, 1528 UID: types.UID(fmt.Sprintf("claim-%d", goRoutineNum)), 1529 }, 1530 Spec: resourcev1alpha2.ResourceClaimSpec{ 1531 ResourceClassName: "test-class", 1532 }, 1533 Status: resourcev1alpha2.ResourceClaimStatus{ 1534 DriverName: driverName, 1535 Allocation: &resourcev1alpha2.AllocationResult{ 1536 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 1537 {Data: "test-data", DriverName: driverName}, 1538 }, 1539 }, 1540 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 1541 {UID: podUID}, 1542 }, 1543 }, 1544 } 1545 1546 if _, err = fakeKubeClient.ResourceV1alpha2().ResourceClaims(pod.Namespace).Create(context.Background(), resourceClaim, metav1.CreateOptions{}); err != nil { 1547 t.Errorf("failed to create ResourceClaim %s: %+v", resourceClaim.Name, err) 1548 return 1549 } 1550 1551 if err = manager.PrepareResources(pod); err != nil { 1552 t.Errorf("pod: %s: PrepareResources failed: %+v", pod.Name, err) 1553 return 1554 } 1555 1556 if err = manager.UnprepareResources(pod); err != nil { 1557 t.Errorf("pod: %s: UnprepareResources failed: %+v", pod.Name, err) 1558 return 1559 } 1560 1561 }(t, i) 1562 } 1563 wgStart.Done() // Start executing goroutines 1564 wgSync.Wait() // Wait for all goroutines to finish 1565 }