k8s.io/kubernetes@v1.29.3/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/atomic" 26 "testing" 27 "time" 28 29 "github.com/stretchr/testify/assert" 30 "google.golang.org/grpc" 31 v1 "k8s.io/api/core/v1" 32 resourcev1alpha2 "k8s.io/api/resource/v1alpha2" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/util/sets" 35 "k8s.io/client-go/kubernetes/fake" 36 "k8s.io/dynamic-resource-allocation/resourceclaim" 37 drapbv1 "k8s.io/kubelet/pkg/apis/dra/v1alpha3" 38 "k8s.io/kubernetes/pkg/kubelet/cm/dra/plugin" 39 "k8s.io/kubernetes/pkg/kubelet/cm/dra/state" 40 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 41 ) 42 43 const ( 44 driverName = "test-cdi-device" 45 driverClassName = "test" 46 ) 47 48 type fakeDRADriverGRPCServer struct { 49 drapbv1.UnimplementedNodeServer 50 driverName string 51 timeout *time.Duration 52 prepareResourceCalls atomic.Uint32 53 unprepareResourceCalls atomic.Uint32 54 } 55 56 func (s *fakeDRADriverGRPCServer) NodePrepareResources(ctx context.Context, req *drapbv1.NodePrepareResourcesRequest) (*drapbv1.NodePrepareResourcesResponse, error) { 57 s.prepareResourceCalls.Add(1) 58 59 if s.timeout != nil { 60 time.Sleep(*s.timeout) 61 } 62 deviceName := "claim-" + req.Claims[0].Uid 63 result := s.driverName + "/" + driverClassName + "=" + deviceName 64 return &drapbv1.NodePrepareResourcesResponse{Claims: map[string]*drapbv1.NodePrepareResourceResponse{req.Claims[0].Uid: {CDIDevices: []string{result}}}}, nil 65 } 66 67 func (s *fakeDRADriverGRPCServer) NodeUnprepareResources(ctx context.Context, req *drapbv1.NodeUnprepareResourcesRequest) (*drapbv1.NodeUnprepareResourcesResponse, error) { 68 s.unprepareResourceCalls.Add(1) 69 70 if s.timeout != nil { 71 time.Sleep(*s.timeout) 72 } 73 return &drapbv1.NodeUnprepareResourcesResponse{Claims: map[string]*drapbv1.NodeUnprepareResourceResponse{req.Claims[0].Uid: {}}}, nil 74 } 75 76 type tearDown func() 77 78 type fakeDRAServerInfo struct { 79 // fake DRA server 80 server *fakeDRADriverGRPCServer 81 // fake DRA plugin socket name 82 socketName string 83 // teardownFn stops fake gRPC server 84 teardownFn tearDown 85 } 86 87 func setupFakeDRADriverGRPCServer(shouldTimeout bool) (fakeDRAServerInfo, error) { 88 socketDir, err := os.MkdirTemp("", "dra") 89 if err != nil { 90 return fakeDRAServerInfo{ 91 server: nil, 92 socketName: "", 93 teardownFn: nil, 94 }, err 95 } 96 97 socketName := filepath.Join(socketDir, "server.sock") 98 stopCh := make(chan struct{}) 99 100 teardown := func() { 101 close(stopCh) 102 os.RemoveAll(socketName) 103 } 104 105 l, err := net.Listen("unix", socketName) 106 if err != nil { 107 teardown() 108 return fakeDRAServerInfo{ 109 server: nil, 110 socketName: "", 111 teardownFn: nil, 112 }, err 113 } 114 115 s := grpc.NewServer() 116 fakeDRADriverGRPCServer := &fakeDRADriverGRPCServer{ 117 driverName: driverName, 118 } 119 if shouldTimeout { 120 timeout := plugin.PluginClientTimeout + time.Second 121 fakeDRADriverGRPCServer.timeout = &timeout 122 } 123 124 drapbv1.RegisterNodeServer(s, fakeDRADriverGRPCServer) 125 126 go func() { 127 go s.Serve(l) 128 <-stopCh 129 s.GracefulStop() 130 }() 131 132 return fakeDRAServerInfo{ 133 server: fakeDRADriverGRPCServer, 134 socketName: socketName, 135 teardownFn: teardown, 136 }, nil 137 } 138 139 func TestNewManagerImpl(t *testing.T) { 140 kubeClient := fake.NewSimpleClientset() 141 for _, test := range []struct { 142 description string 143 stateFileDirectory string 144 wantErr bool 145 }{ 146 { 147 description: "invalid directory path", 148 stateFileDirectory: "", 149 wantErr: true, 150 }, 151 { 152 description: "valid directory path", 153 stateFileDirectory: t.TempDir(), 154 }, 155 } { 156 t.Run(test.description, func(t *testing.T) { 157 manager, err := NewManagerImpl(kubeClient, test.stateFileDirectory) 158 if test.wantErr { 159 assert.Error(t, err) 160 return 161 } 162 163 assert.NoError(t, err) 164 assert.NotNil(t, manager.cache) 165 assert.NotNil(t, manager.kubeClient) 166 }) 167 } 168 } 169 170 func TestGetResources(t *testing.T) { 171 kubeClient := fake.NewSimpleClientset() 172 resourceClaimName := "test-pod-claim-1" 173 174 for _, test := range []struct { 175 description string 176 container *v1.Container 177 pod *v1.Pod 178 claimInfo *ClaimInfo 179 wantErr bool 180 }{ 181 { 182 description: "claim info with annotations", 183 container: &v1.Container{ 184 Name: "test-container", 185 Resources: v1.ResourceRequirements{ 186 Claims: []v1.ResourceClaim{ 187 { 188 Name: "test-pod-claim-1", 189 }, 190 }, 191 }, 192 }, 193 pod: &v1.Pod{ 194 ObjectMeta: metav1.ObjectMeta{ 195 Name: "test-pod", 196 Namespace: "test-namespace", 197 }, 198 Spec: v1.PodSpec{ 199 ResourceClaims: []v1.PodResourceClaim{ 200 { 201 Name: "test-pod-claim-1", 202 Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName}, 203 }, 204 }, 205 }, 206 }, 207 claimInfo: &ClaimInfo{ 208 annotations: map[string][]kubecontainer.Annotation{ 209 "test-plugin": { 210 { 211 Name: "test-annotation", 212 Value: "123", 213 }, 214 }, 215 }, 216 ClaimInfoState: state.ClaimInfoState{ 217 ClaimName: "test-pod-claim-1", 218 CDIDevices: map[string][]string{ 219 driverName: {"123"}, 220 }, 221 Namespace: "test-namespace", 222 }, 223 }, 224 }, 225 { 226 description: "claim info without annotations", 227 container: &v1.Container{ 228 Name: "test-container", 229 Resources: v1.ResourceRequirements{ 230 Claims: []v1.ResourceClaim{ 231 { 232 Name: "test-pod-claim-1", 233 }, 234 }, 235 }, 236 }, 237 pod: &v1.Pod{ 238 ObjectMeta: metav1.ObjectMeta{ 239 Name: "test-pod", 240 Namespace: "test-namespace", 241 }, 242 Spec: v1.PodSpec{ 243 ResourceClaims: []v1.PodResourceClaim{ 244 { 245 Name: "test-pod-claim-1", 246 Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName}, 247 }, 248 }, 249 }, 250 }, 251 claimInfo: &ClaimInfo{ 252 ClaimInfoState: state.ClaimInfoState{ 253 ClaimName: "test-pod-claim-1", 254 CDIDevices: map[string][]string{ 255 driverName: {"123"}, 256 }, 257 Namespace: "test-namespace", 258 }, 259 }, 260 }, 261 { 262 description: "no claim info", 263 container: &v1.Container{ 264 Name: "test-container", 265 Resources: v1.ResourceRequirements{ 266 Claims: []v1.ResourceClaim{ 267 { 268 Name: "test-pod-claim-1", 269 }, 270 }, 271 }, 272 }, 273 pod: &v1.Pod{ 274 ObjectMeta: metav1.ObjectMeta{ 275 Name: "test-pod", 276 Namespace: "test-namespace", 277 }, 278 Spec: v1.PodSpec{ 279 ResourceClaims: []v1.PodResourceClaim{ 280 { 281 Name: "test-pod-claim-1", 282 }, 283 }, 284 }, 285 }, 286 wantErr: true, 287 }, 288 } { 289 t.Run(test.description, func(t *testing.T) { 290 manager, err := NewManagerImpl(kubeClient, t.TempDir()) 291 assert.NoError(t, err) 292 293 if test.claimInfo != nil { 294 manager.cache.add(test.claimInfo) 295 } 296 297 containerInfo, err := manager.GetResources(test.pod, test.container) 298 if test.wantErr { 299 assert.Error(t, err) 300 return 301 } 302 303 assert.NoError(t, err) 304 assert.Equal(t, test.claimInfo.CDIDevices[driverName][0], containerInfo.CDIDevices[0].Name) 305 }) 306 } 307 } 308 309 func TestPrepareResources(t *testing.T) { 310 fakeKubeClient := fake.NewSimpleClientset() 311 312 for _, test := range []struct { 313 description string 314 driverName string 315 pod *v1.Pod 316 claimInfo *ClaimInfo 317 resourceClaim *resourcev1alpha2.ResourceClaim 318 wantErr bool 319 wantTimeout bool 320 wantResourceSkipped bool 321 ExpectedPrepareCalls uint32 322 }{ 323 { 324 description: "failed to fetch ResourceClaim", 325 driverName: driverName, 326 pod: &v1.Pod{ 327 ObjectMeta: metav1.ObjectMeta{ 328 Name: "test-pod", 329 Namespace: "test-namespace", 330 UID: "test-reserved", 331 }, 332 Spec: v1.PodSpec{ 333 ResourceClaims: []v1.PodResourceClaim{ 334 { 335 Name: "test-pod-claim-0", 336 Source: v1.ClaimSource{ 337 ResourceClaimName: func() *string { 338 s := "test-pod-claim-0" 339 return &s 340 }(), 341 }, 342 }, 343 }, 344 }, 345 }, 346 wantErr: true, 347 }, 348 { 349 description: "plugin does not exist", 350 driverName: "this-plugin-does-not-exist", 351 pod: &v1.Pod{ 352 ObjectMeta: metav1.ObjectMeta{ 353 Name: "test-pod", 354 Namespace: "test-namespace", 355 UID: "test-reserved", 356 }, 357 Spec: v1.PodSpec{ 358 ResourceClaims: []v1.PodResourceClaim{ 359 { 360 Name: "test-pod-claim-1", 361 Source: v1.ClaimSource{ 362 ResourceClaimName: func() *string { 363 s := "test-pod-claim-1" 364 return &s 365 }(), 366 }, 367 }, 368 }, 369 Containers: []v1.Container{ 370 { 371 Resources: v1.ResourceRequirements{ 372 Claims: []v1.ResourceClaim{ 373 { 374 Name: "test-pod-claim-1", 375 }, 376 }, 377 }, 378 }, 379 }, 380 }, 381 }, 382 resourceClaim: &resourcev1alpha2.ResourceClaim{ 383 ObjectMeta: metav1.ObjectMeta{ 384 Name: "test-pod-claim-1", 385 Namespace: "test-namespace", 386 UID: "test-reserved", 387 }, 388 Spec: resourcev1alpha2.ResourceClaimSpec{ 389 ResourceClassName: "test-class", 390 }, 391 Status: resourcev1alpha2.ResourceClaimStatus{ 392 DriverName: driverName, 393 Allocation: &resourcev1alpha2.AllocationResult{ 394 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 395 {Data: "test-data", DriverName: driverName}, 396 }, 397 }, 398 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 399 {UID: "test-reserved"}, 400 }, 401 }, 402 }, 403 wantErr: true, 404 }, 405 { 406 description: "pod is not allowed to use resource claim", 407 driverName: driverName, 408 pod: &v1.Pod{ 409 ObjectMeta: metav1.ObjectMeta{ 410 Name: "test-pod", 411 Namespace: "test-namespace", 412 UID: "test-reserved", 413 }, 414 Spec: v1.PodSpec{ 415 ResourceClaims: []v1.PodResourceClaim{ 416 { 417 Name: "test-pod-claim-2", 418 Source: v1.ClaimSource{ 419 ResourceClaimName: func() *string { 420 s := "test-pod-claim-2" 421 return &s 422 }(), 423 }, 424 }, 425 }, 426 }, 427 }, 428 resourceClaim: &resourcev1alpha2.ResourceClaim{ 429 ObjectMeta: metav1.ObjectMeta{ 430 Name: "test-pod-claim-2", 431 Namespace: "test-namespace", 432 UID: "test-reserved", 433 }, 434 Spec: resourcev1alpha2.ResourceClaimSpec{ 435 ResourceClassName: "test-class", 436 }, 437 Status: resourcev1alpha2.ResourceClaimStatus{ 438 DriverName: driverName, 439 Allocation: &resourcev1alpha2.AllocationResult{ 440 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 441 {Data: "test-data", DriverName: driverName}, 442 }, 443 }, 444 }, 445 }, 446 wantErr: true, 447 }, 448 { 449 description: "no container actually uses the claim", 450 driverName: driverName, 451 pod: &v1.Pod{ 452 ObjectMeta: metav1.ObjectMeta{ 453 Name: "test-pod", 454 Namespace: "test-namespace", 455 UID: "test-reserved", 456 }, 457 Spec: v1.PodSpec{ 458 ResourceClaims: []v1.PodResourceClaim{ 459 { 460 Name: "test-pod-claim-3", 461 Source: v1.ClaimSource{ResourceClaimName: func() *string { 462 s := "test-pod-claim-3" 463 return &s 464 }()}, 465 }, 466 }, 467 }, 468 }, 469 resourceClaim: &resourcev1alpha2.ResourceClaim{ 470 ObjectMeta: metav1.ObjectMeta{ 471 Name: "test-pod-claim-3", 472 Namespace: "test-namespace", 473 UID: "test-reserved", 474 }, 475 Spec: resourcev1alpha2.ResourceClaimSpec{ 476 ResourceClassName: "test-class", 477 }, 478 Status: resourcev1alpha2.ResourceClaimStatus{ 479 DriverName: driverName, 480 Allocation: &resourcev1alpha2.AllocationResult{ 481 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 482 {Data: "test-data", DriverName: driverName}, 483 }, 484 }, 485 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 486 {UID: "test-reserved"}, 487 }, 488 }, 489 }, 490 wantResourceSkipped: true, 491 }, 492 { 493 description: "resource already prepared", 494 driverName: driverName, 495 pod: &v1.Pod{ 496 ObjectMeta: metav1.ObjectMeta{ 497 Name: "test-pod", 498 Namespace: "test-namespace", 499 UID: "test-reserved", 500 }, 501 Spec: v1.PodSpec{ 502 ResourceClaims: []v1.PodResourceClaim{ 503 { 504 Name: "test-pod-claim-4", 505 Source: v1.ClaimSource{ResourceClaimName: func() *string { 506 s := "test-pod-claim-4" 507 return &s 508 }()}, 509 }, 510 }, 511 Containers: []v1.Container{ 512 { 513 Resources: v1.ResourceRequirements{ 514 Claims: []v1.ResourceClaim{ 515 { 516 Name: "test-pod-claim-4", 517 }, 518 }, 519 }, 520 }, 521 }, 522 }, 523 }, 524 claimInfo: &ClaimInfo{ 525 ClaimInfoState: state.ClaimInfoState{ 526 DriverName: driverName, 527 ClaimName: "test-pod-claim-4", 528 Namespace: "test-namespace", 529 PodUIDs: sets.Set[string]{"test-another-pod-reserved": sets.Empty{}}, 530 }, 531 prepared: true, 532 }, 533 resourceClaim: &resourcev1alpha2.ResourceClaim{ 534 ObjectMeta: metav1.ObjectMeta{ 535 Name: "test-pod-claim-4", 536 Namespace: "test-namespace", 537 UID: "test-reserved", 538 }, 539 Spec: resourcev1alpha2.ResourceClaimSpec{ 540 ResourceClassName: "test-class", 541 }, 542 Status: resourcev1alpha2.ResourceClaimStatus{ 543 DriverName: driverName, 544 Allocation: &resourcev1alpha2.AllocationResult{ 545 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 546 {Data: "test-data", DriverName: driverName}, 547 }, 548 }, 549 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 550 {UID: "test-reserved"}, 551 }, 552 }, 553 }, 554 wantResourceSkipped: true, 555 }, 556 { 557 description: "should timeout", 558 driverName: driverName, 559 pod: &v1.Pod{ 560 ObjectMeta: metav1.ObjectMeta{ 561 Name: "test-pod", 562 Namespace: "test-namespace", 563 UID: "test-reserved", 564 }, 565 Spec: v1.PodSpec{ 566 ResourceClaims: []v1.PodResourceClaim{ 567 { 568 Name: "test-pod-claim-5", 569 Source: v1.ClaimSource{ResourceClaimName: func() *string { 570 s := "test-pod-claim-5" 571 return &s 572 }()}, 573 }, 574 }, 575 Containers: []v1.Container{ 576 { 577 Resources: v1.ResourceRequirements{ 578 Claims: []v1.ResourceClaim{ 579 { 580 Name: "test-pod-claim-5", 581 }, 582 }, 583 }, 584 }, 585 }, 586 }, 587 }, 588 resourceClaim: &resourcev1alpha2.ResourceClaim{ 589 ObjectMeta: metav1.ObjectMeta{ 590 Name: "test-pod-claim-5", 591 Namespace: "test-namespace", 592 UID: "test-reserved", 593 }, 594 Spec: resourcev1alpha2.ResourceClaimSpec{ 595 ResourceClassName: "test-class", 596 }, 597 Status: resourcev1alpha2.ResourceClaimStatus{ 598 DriverName: driverName, 599 Allocation: &resourcev1alpha2.AllocationResult{ 600 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 601 {Data: "test-data", DriverName: driverName}, 602 }, 603 }, 604 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 605 {UID: "test-reserved"}, 606 }, 607 }, 608 }, 609 wantErr: true, 610 wantTimeout: true, 611 ExpectedPrepareCalls: 1, 612 }, 613 { 614 description: "should prepare resource, claim not in cache", 615 driverName: driverName, 616 pod: &v1.Pod{ 617 ObjectMeta: metav1.ObjectMeta{ 618 Name: "test-pod", 619 Namespace: "test-namespace", 620 UID: "test-reserved", 621 }, 622 Spec: v1.PodSpec{ 623 ResourceClaims: []v1.PodResourceClaim{ 624 { 625 Name: "test-pod-claim-6", 626 Source: v1.ClaimSource{ResourceClaimName: func() *string { 627 s := "test-pod-claim-6" 628 return &s 629 }()}, 630 }, 631 }, 632 Containers: []v1.Container{ 633 { 634 Resources: v1.ResourceRequirements{ 635 Claims: []v1.ResourceClaim{ 636 { 637 Name: "test-pod-claim-6", 638 }, 639 }, 640 }, 641 }, 642 }, 643 }, 644 }, 645 resourceClaim: &resourcev1alpha2.ResourceClaim{ 646 ObjectMeta: metav1.ObjectMeta{ 647 Name: "test-pod-claim-6", 648 Namespace: "test-namespace", 649 UID: "test-reserved", 650 }, 651 Spec: resourcev1alpha2.ResourceClaimSpec{ 652 ResourceClassName: "test-class", 653 }, 654 Status: resourcev1alpha2.ResourceClaimStatus{ 655 DriverName: driverName, 656 Allocation: &resourcev1alpha2.AllocationResult{ 657 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 658 {Data: "test-data"}, 659 }, 660 }, 661 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 662 {UID: "test-reserved"}, 663 }, 664 }, 665 }, 666 ExpectedPrepareCalls: 1, 667 }, 668 { 669 description: "should prepare resource. claim in cache, manager did not prepare resource", 670 driverName: driverName, 671 pod: &v1.Pod{ 672 ObjectMeta: metav1.ObjectMeta{ 673 Name: "test-pod", 674 Namespace: "test-namespace", 675 UID: "test-reserved", 676 }, 677 Spec: v1.PodSpec{ 678 ResourceClaims: []v1.PodResourceClaim{ 679 { 680 Name: "test-pod-claim", 681 Source: v1.ClaimSource{ResourceClaimName: func() *string { 682 s := "test-pod-claim" 683 return &s 684 }()}, 685 }, 686 }, 687 Containers: []v1.Container{ 688 { 689 Resources: v1.ResourceRequirements{ 690 Claims: []v1.ResourceClaim{ 691 { 692 Name: "test-pod-claim", 693 }, 694 }, 695 }, 696 }, 697 }, 698 }, 699 }, 700 claimInfo: &ClaimInfo{ 701 ClaimInfoState: state.ClaimInfoState{ 702 DriverName: driverName, 703 ClassName: "test-class", 704 ClaimName: "test-pod-claim", 705 ClaimUID: "test-reserved", 706 Namespace: "test-namespace", 707 PodUIDs: sets.Set[string]{"test-reserved": sets.Empty{}}, 708 CDIDevices: map[string][]string{ 709 driverName: {fmt.Sprintf("%s/%s=some-device", driverName, driverClassName)}, 710 }, 711 ResourceHandles: []resourcev1alpha2.ResourceHandle{{Data: "test-data"}}, 712 }, 713 annotations: make(map[string][]kubecontainer.Annotation), 714 prepared: false, 715 }, 716 resourceClaim: &resourcev1alpha2.ResourceClaim{ 717 ObjectMeta: metav1.ObjectMeta{ 718 Name: "test-pod-claim", 719 Namespace: "test-namespace", 720 UID: "test-reserved", 721 }, 722 Spec: resourcev1alpha2.ResourceClaimSpec{ 723 ResourceClassName: "test-class", 724 }, 725 Status: resourcev1alpha2.ResourceClaimStatus{ 726 DriverName: driverName, 727 Allocation: &resourcev1alpha2.AllocationResult{ 728 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 729 {Data: "test-data"}, 730 }, 731 }, 732 ReservedFor: []resourcev1alpha2.ResourceClaimConsumerReference{ 733 {UID: "test-reserved"}, 734 }, 735 }, 736 }, 737 ExpectedPrepareCalls: 1, 738 }, 739 } { 740 t.Run(test.description, func(t *testing.T) { 741 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 742 if err != nil { 743 t.Fatalf("failed to newClaimInfoCache, err:%v", err) 744 } 745 746 manager := &ManagerImpl{ 747 kubeClient: fakeKubeClient, 748 cache: cache, 749 } 750 751 if test.resourceClaim != nil { 752 if _, err := fakeKubeClient.ResourceV1alpha2().ResourceClaims(test.pod.Namespace).Create(context.Background(), test.resourceClaim, metav1.CreateOptions{}); err != nil { 753 t.Fatalf("failed to create ResourceClaim %s: %+v", test.resourceClaim.Name, err) 754 } 755 } 756 757 draServerInfo, err := setupFakeDRADriverGRPCServer(test.wantTimeout) 758 if err != nil { 759 t.Fatal(err) 760 } 761 defer draServerInfo.teardownFn() 762 763 plg := plugin.NewRegistrationHandler() 764 if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{"1.27"}); err != nil { 765 t.Fatalf("failed to register plugin %s, err: %v", test.driverName, err) 766 } 767 defer plg.DeRegisterPlugin(test.driverName) // for sake of next tests 768 769 if test.claimInfo != nil { 770 manager.cache.add(test.claimInfo) 771 } 772 773 err = manager.PrepareResources(test.pod) 774 775 assert.Equal(t, test.ExpectedPrepareCalls, draServerInfo.server.prepareResourceCalls.Load()) 776 777 if test.wantErr { 778 assert.Error(t, err) 779 return // PrepareResources returned an error so stopping the subtest here 780 } else if test.wantResourceSkipped { 781 assert.NoError(t, err) 782 return // resource skipped so no need to continue 783 } 784 785 assert.NoError(t, err) 786 // check the cache contains the expected claim info 787 claimName, _, err := resourceclaim.Name(test.pod, &test.pod.Spec.ResourceClaims[0]) 788 if err != nil { 789 t.Fatal(err) 790 } 791 claimInfo := manager.cache.get(*claimName, test.pod.Namespace) 792 if claimInfo == nil { 793 t.Fatalf("claimInfo not found in cache for claim %s", *claimName) 794 } 795 if claimInfo.DriverName != test.resourceClaim.Status.DriverName { 796 t.Fatalf("driverName mismatch: expected %s, got %s", test.resourceClaim.Status.DriverName, claimInfo.DriverName) 797 } 798 if claimInfo.ClassName != test.resourceClaim.Spec.ResourceClassName { 799 t.Fatalf("resourceClassName mismatch: expected %s, got %s", test.resourceClaim.Spec.ResourceClassName, claimInfo.ClassName) 800 } 801 if len(claimInfo.PodUIDs) != 1 || !claimInfo.PodUIDs.Has(string(test.pod.UID)) { 802 t.Fatalf("podUIDs mismatch: expected [%s], got %v", test.pod.UID, claimInfo.PodUIDs) 803 } 804 expectedResourceClaimDriverName := fmt.Sprintf("%s/%s=claim-%s", driverName, driverClassName, string(test.resourceClaim.Status.ReservedFor[0].UID)) 805 if len(claimInfo.CDIDevices[test.resourceClaim.Status.DriverName]) != 1 || claimInfo.CDIDevices[test.resourceClaim.Status.DriverName][0] != expectedResourceClaimDriverName { 806 t.Fatalf("cdiDevices mismatch: expected [%s], got %v", []string{expectedResourceClaimDriverName}, claimInfo.CDIDevices[test.resourceClaim.Status.DriverName]) 807 } 808 }) 809 } 810 } 811 812 func TestUnprepareResources(t *testing.T) { 813 fakeKubeClient := fake.NewSimpleClientset() 814 815 for _, test := range []struct { 816 description string 817 driverName string 818 pod *v1.Pod 819 claimInfo *ClaimInfo 820 wantErr bool 821 wantTimeout bool 822 wantResourceSkipped bool 823 expectedUnprepareCalls uint32 824 }{ 825 { 826 description: "plugin does not exist", 827 driverName: "this-plugin-does-not-exist", 828 pod: &v1.Pod{ 829 ObjectMeta: metav1.ObjectMeta{ 830 Name: "test-pod", 831 Namespace: "test-namespace", 832 UID: "test-reserved", 833 }, 834 Spec: v1.PodSpec{ 835 ResourceClaims: []v1.PodResourceClaim{ 836 { 837 Name: "another-claim-test", 838 Source: v1.ClaimSource{ 839 ResourceClaimName: func() *string { 840 s := "another-claim-test" 841 return &s 842 }(), 843 }, 844 }, 845 }, 846 }, 847 }, 848 claimInfo: &ClaimInfo{ 849 ClaimInfoState: state.ClaimInfoState{ 850 DriverName: driverName, 851 ClaimName: "another-claim-test", 852 Namespace: "test-namespace", 853 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 854 { 855 DriverName: driverName, 856 Data: "test data", 857 }, 858 }, 859 }, 860 }, 861 wantErr: true, 862 }, 863 { 864 description: "resource claim referenced by other pod(s)", 865 driverName: driverName, 866 pod: &v1.Pod{ 867 ObjectMeta: metav1.ObjectMeta{ 868 Name: "test-pod", 869 Namespace: "test-namespace", 870 UID: "test-reserved", 871 }, 872 Spec: v1.PodSpec{ 873 ResourceClaims: []v1.PodResourceClaim{ 874 { 875 Name: "test-pod-claim-1", 876 Source: v1.ClaimSource{ResourceClaimName: func() *string { 877 s := "test-pod-claim-1" 878 return &s 879 }()}, 880 }, 881 }, 882 Containers: []v1.Container{ 883 { 884 Resources: v1.ResourceRequirements{ 885 Claims: []v1.ResourceClaim{ 886 { 887 Name: "test-pod-claim-1", 888 }, 889 }, 890 }, 891 }, 892 }, 893 }, 894 }, 895 claimInfo: &ClaimInfo{ 896 ClaimInfoState: state.ClaimInfoState{ 897 DriverName: driverName, 898 ClaimName: "test-pod-claim-1", 899 Namespace: "test-namespace", 900 PodUIDs: sets.Set[string]{"test-reserved": sets.Empty{}, "test-reserved-2": sets.Empty{}}, 901 }, 902 }, 903 wantResourceSkipped: true, 904 }, 905 { 906 description: "should timeout", 907 driverName: driverName, 908 pod: &v1.Pod{ 909 ObjectMeta: metav1.ObjectMeta{ 910 Name: "test-pod", 911 Namespace: "test-namespace", 912 UID: "test-reserved", 913 }, 914 Spec: v1.PodSpec{ 915 ResourceClaims: []v1.PodResourceClaim{ 916 { 917 Name: "test-pod-claim-2", 918 Source: v1.ClaimSource{ResourceClaimName: func() *string { 919 s := "test-pod-claim-2" 920 return &s 921 }()}, 922 }, 923 }, 924 Containers: []v1.Container{ 925 { 926 Resources: v1.ResourceRequirements{ 927 Claims: []v1.ResourceClaim{ 928 { 929 Name: "test-pod-claim-2", 930 }, 931 }, 932 }, 933 }, 934 }, 935 }, 936 }, 937 claimInfo: &ClaimInfo{ 938 ClaimInfoState: state.ClaimInfoState{ 939 DriverName: driverName, 940 ClaimName: "test-pod-claim-2", 941 Namespace: "test-namespace", 942 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 943 { 944 DriverName: driverName, 945 Data: "test data", 946 }, 947 }, 948 }, 949 }, 950 wantErr: true, 951 wantTimeout: true, 952 expectedUnprepareCalls: 1, 953 }, 954 { 955 description: "should unprepare resource, claim previously prepared by currently running manager", 956 driverName: driverName, 957 pod: &v1.Pod{ 958 ObjectMeta: metav1.ObjectMeta{ 959 Name: "test-pod", 960 Namespace: "test-namespace", 961 UID: "test-reserved", 962 }, 963 Spec: v1.PodSpec{ 964 ResourceClaims: []v1.PodResourceClaim{ 965 { 966 Name: "test-pod-claim-3", 967 Source: v1.ClaimSource{ResourceClaimName: func() *string { 968 s := "test-pod-claim-3" 969 return &s 970 }()}, 971 }, 972 }, 973 Containers: []v1.Container{ 974 { 975 Resources: v1.ResourceRequirements{ 976 Claims: []v1.ResourceClaim{ 977 { 978 Name: "test-pod-claim-3", 979 }, 980 }, 981 }, 982 }, 983 }, 984 }, 985 }, 986 claimInfo: &ClaimInfo{ 987 ClaimInfoState: state.ClaimInfoState{ 988 DriverName: driverName, 989 ClaimName: "test-pod-claim-3", 990 Namespace: "test-namespace", 991 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 992 { 993 DriverName: driverName, 994 Data: "test data", 995 }, 996 }, 997 }, 998 prepared: true, 999 }, 1000 expectedUnprepareCalls: 1, 1001 }, 1002 { 1003 description: "should unprepare resource, claim previously was not prepared by currently running manager", 1004 driverName: driverName, 1005 pod: &v1.Pod{ 1006 ObjectMeta: metav1.ObjectMeta{ 1007 Name: "test-pod", 1008 Namespace: "test-namespace", 1009 UID: "test-reserved", 1010 }, 1011 Spec: v1.PodSpec{ 1012 ResourceClaims: []v1.PodResourceClaim{ 1013 { 1014 Name: "test-pod-claim", 1015 Source: v1.ClaimSource{ResourceClaimName: func() *string { 1016 s := "test-pod-claim" 1017 return &s 1018 }()}, 1019 }, 1020 }, 1021 Containers: []v1.Container{ 1022 { 1023 Resources: v1.ResourceRequirements{ 1024 Claims: []v1.ResourceClaim{ 1025 { 1026 Name: "test-pod-claim", 1027 }, 1028 }, 1029 }, 1030 }, 1031 }, 1032 }, 1033 }, 1034 claimInfo: &ClaimInfo{ 1035 ClaimInfoState: state.ClaimInfoState{ 1036 DriverName: driverName, 1037 ClaimName: "test-pod-claim", 1038 Namespace: "test-namespace", 1039 ResourceHandles: []resourcev1alpha2.ResourceHandle{ 1040 { 1041 DriverName: driverName, 1042 Data: "test data", 1043 }, 1044 }, 1045 }, 1046 prepared: false, 1047 }, 1048 expectedUnprepareCalls: 1, 1049 }, 1050 } { 1051 t.Run(test.description, func(t *testing.T) { 1052 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 1053 if err != nil { 1054 t.Fatalf("failed to create a new instance of the claimInfoCache, err: %v", err) 1055 } 1056 1057 draServerInfo, err := setupFakeDRADriverGRPCServer(test.wantTimeout) 1058 if err != nil { 1059 t.Fatal(err) 1060 } 1061 defer draServerInfo.teardownFn() 1062 1063 plg := plugin.NewRegistrationHandler() 1064 if err := plg.RegisterPlugin(test.driverName, draServerInfo.socketName, []string{"1.27"}); err != nil { 1065 t.Fatalf("failed to register plugin %s, err: %v", test.driverName, err) 1066 } 1067 defer plg.DeRegisterPlugin(test.driverName) // for sake of next tests 1068 1069 manager := &ManagerImpl{ 1070 kubeClient: fakeKubeClient, 1071 cache: cache, 1072 } 1073 1074 if test.claimInfo != nil { 1075 manager.cache.add(test.claimInfo) 1076 } 1077 1078 err = manager.UnprepareResources(test.pod) 1079 1080 assert.Equal(t, test.expectedUnprepareCalls, draServerInfo.server.unprepareResourceCalls.Load()) 1081 1082 if test.wantErr { 1083 assert.Error(t, err) 1084 return // UnprepareResources returned an error so stopping the subtest here 1085 } else if test.wantResourceSkipped { 1086 assert.NoError(t, err) 1087 return // resource skipped so no need to continue 1088 } 1089 1090 assert.NoError(t, err) 1091 // Check that the cache has been updated correctly 1092 claimName, _, err := resourceclaim.Name(test.pod, &test.pod.Spec.ResourceClaims[0]) 1093 if err != nil { 1094 t.Fatal(err) 1095 } 1096 claimInfo := manager.cache.get(*claimName, test.pod.Namespace) 1097 if claimInfo != nil { 1098 t.Fatalf("claimInfo still found in cache after calling UnprepareResources") 1099 } 1100 }) 1101 } 1102 } 1103 1104 func TestPodMightNeedToUnprepareResources(t *testing.T) { 1105 fakeKubeClient := fake.NewSimpleClientset() 1106 1107 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 1108 if err != nil { 1109 t.Fatalf("failed to newClaimInfoCache, err:%v", err) 1110 } 1111 1112 manager := &ManagerImpl{ 1113 kubeClient: fakeKubeClient, 1114 cache: cache, 1115 } 1116 1117 podUID := sets.Set[string]{} 1118 podUID.Insert("test-pod-uid") 1119 manager.cache.add(&ClaimInfo{ 1120 ClaimInfoState: state.ClaimInfoState{PodUIDs: podUID, ClaimName: "test-claim", Namespace: "test-namespace"}, 1121 }) 1122 1123 testClaimInfo := manager.cache.get("test-claim", "test-namespace") 1124 testClaimInfo.addPodReference("test-pod-uid") 1125 1126 manager.PodMightNeedToUnprepareResources("test-pod-uid") 1127 } 1128 1129 func TestGetContainerClaimInfos(t *testing.T) { 1130 cache, err := newClaimInfoCache(t.TempDir(), draManagerStateFileName) 1131 if err != nil { 1132 t.Fatalf("error occur:%v", err) 1133 } 1134 manager := &ManagerImpl{ 1135 cache: cache, 1136 } 1137 1138 resourceClaimName := "test-resource-claim-1" 1139 resourceClaimName2 := "test-resource-claim-2" 1140 1141 for i, test := range []struct { 1142 expectedClaimName string 1143 pod *v1.Pod 1144 container *v1.Container 1145 claimInfo *ClaimInfo 1146 }{ 1147 { 1148 expectedClaimName: resourceClaimName, 1149 pod: &v1.Pod{ 1150 Spec: v1.PodSpec{ 1151 ResourceClaims: []v1.PodResourceClaim{ 1152 { 1153 Name: "claim1", 1154 Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName}, 1155 }, 1156 }, 1157 }, 1158 }, 1159 container: &v1.Container{ 1160 Resources: v1.ResourceRequirements{ 1161 Claims: []v1.ResourceClaim{ 1162 { 1163 Name: "claim1", 1164 }, 1165 }, 1166 }, 1167 }, 1168 claimInfo: &ClaimInfo{ClaimInfoState: state.ClaimInfoState{ClaimName: resourceClaimName}}, 1169 }, 1170 { 1171 expectedClaimName: resourceClaimName2, 1172 pod: &v1.Pod{ 1173 Spec: v1.PodSpec{ 1174 ResourceClaims: []v1.PodResourceClaim{ 1175 { 1176 Name: "claim2", 1177 Source: v1.ClaimSource{ResourceClaimName: &resourceClaimName2}, 1178 }, 1179 }, 1180 }, 1181 }, 1182 container: &v1.Container{ 1183 Resources: v1.ResourceRequirements{ 1184 Claims: []v1.ResourceClaim{ 1185 { 1186 Name: "claim2", 1187 }, 1188 }, 1189 }, 1190 }, 1191 claimInfo: &ClaimInfo{ClaimInfoState: state.ClaimInfoState{ClaimName: resourceClaimName2}}, 1192 }, 1193 } { 1194 t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { 1195 manager.cache.add(test.claimInfo) 1196 1197 fakeClaimInfos, err := manager.GetContainerClaimInfos(test.pod, test.container) 1198 assert.NoError(t, err) 1199 assert.Equal(t, 1, len(fakeClaimInfos)) 1200 assert.Equal(t, test.expectedClaimName, fakeClaimInfos[0].ClaimInfoState.ClaimName) 1201 1202 manager.cache.delete(test.pod.Spec.ResourceClaims[0].Name, "default") 1203 _, err = manager.GetContainerClaimInfos(test.pod, test.container) 1204 assert.NoError(t, err) 1205 }) 1206 } 1207 }