@@ -17,6 +17,8 @@ import (
17
17
e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
18
18
storageframework "k8s.io/kubernetes/test/e2e/storage/framework"
19
19
admissionapi "k8s.io/pod-security-admission/api"
20
+
21
+ "github.com/scality/mountpoint-s3-csi-driver/tests/e2e/pkg/s3client"
20
22
)
21
23
22
24
// s3CSIMountOptionsTestSuite implements the Kubernetes storage framework TestSuite interface.
@@ -437,4 +439,314 @@ func (t *s3CSIMountOptionsTestSuite) DefineTests(driver storageframework.TestDri
437
439
ginkgo .By ("Verifying read from the volume" )
438
440
e2evolume .VerifyExecInPodSucceed (f , pod , fmt .Sprintf ("cat %s | grep -q '%s'" , fileInVol , testContent ))
439
441
})
442
+
443
+ // This test verifies that when a volume is mounted with the --prefix option,
444
+ // but no files are created, the prefix doesn't exist in the bucket.
445
+ //
446
+ // Test scenario:
447
+ //
448
+ // [Pod]
449
+ // |
450
+ // ↓
451
+ // [S3 Volume with --prefix=test-prefix/]
452
+ // |
453
+ // ↓
454
+ // [No prefix created]
455
+ //
456
+ // The test specifically checks:
457
+ // 1. The prefix doesn't exist in the bucket before mounting
458
+ // 2. The volume with prefix option can be successfully mounted and accessed
459
+ // 3. The prefix still doesn't exist in the bucket after mounting
460
+ //
461
+ // This validates that the S3 CSI driver doesn't implicitly create the prefix
462
+ // in the S3 bucket just by mounting with the prefix option.
463
+ ginkgo .It ("should not create prefix in bucket when no files are created" , func (ctx context.Context ) {
464
+ // We need to access the S3 client directly to verify objects
465
+ s3Client := s3client .New ("" , "" , "" ) // Default credentials/region
466
+ var err error
467
+
468
+ // Create volume with standard non-root options plus prefix option
469
+ prefix := "empty-prefix/"
470
+ resource := BuildVolumeWithOptions (
471
+ ctx ,
472
+ l .config ,
473
+ pattern ,
474
+ DefaultNonRootUser ,
475
+ DefaultNonRootGroup ,
476
+ "" , // No specific file mode
477
+ fmt .Sprintf ("prefix=%s" , prefix ), // Add the prefix option
478
+ )
479
+ l .resources = append (l .resources , resource )
480
+
481
+ // Extract the bucket name from the volume for verification
482
+ bucketName := GetBucketNameFromVolumeResource (resource )
483
+ if bucketName == "" {
484
+ framework .Failf ("Failed to extract bucket name from volume resource" )
485
+ }
486
+
487
+ // List all objects in the bucket to verify the prefix doesn't exist BEFORE mounting
488
+ ginkgo .By ("Verifying prefix doesn't exist in bucket before mounting" )
489
+ rootListOutputBefore , err := s3Client .ListObjects (ctx , bucketName )
490
+ framework .ExpectNoError (err , "Failed to list objects in bucket before mounting" )
491
+
492
+ // Check if any objects with the prefix exist before mounting
493
+ prefixExistsBefore := false
494
+ for _ , obj := range rootListOutputBefore .Contents {
495
+ if strings .HasPrefix (* obj .Key , prefix ) {
496
+ prefixExistsBefore = true
497
+ break
498
+ }
499
+ }
500
+
501
+ if prefixExistsBefore {
502
+ framework .Failf ("Prefix %s already exists in bucket %s before mounting" , prefix , bucketName )
503
+ } else {
504
+ framework .Logf ("Verified prefix %s does not exist in bucket %s before mounting" , prefix , bucketName )
505
+ }
506
+
507
+ // Create pod with the prefixed volume
508
+ ginkgo .By ("Creating pod with a volume using prefix option" )
509
+ pod := MakeNonRootPodWithVolume (f .Namespace .Name , []* v1.PersistentVolumeClaim {resource .Pvc }, "" )
510
+ pod , err = createPod (ctx , f .ClientSet , f .Namespace .Name , pod )
511
+ framework .ExpectNoError (err )
512
+ defer func () {
513
+ framework .ExpectNoError (e2epod .DeletePodWithWait (ctx , f .ClientSet , pod ))
514
+ }()
515
+
516
+ // Verify the mount point exists and is accessible
517
+ volPath := "/mnt/volume1"
518
+ ginkgo .By ("Verifying volume is mounted and accessible" )
519
+ e2evolume .VerifyExecInPodSucceed (f , pod , fmt .Sprintf ("ls -la %s" , volPath ))
520
+
521
+ // List all objects in the bucket to verify the prefix doesn't exist AFTER mounting
522
+ ginkgo .By ("Verifying prefix doesn't exist in bucket after mounting" )
523
+ rootListOutputAfter , err := s3Client .ListObjects (ctx , bucketName )
524
+ framework .ExpectNoError (err , "Failed to list objects in bucket after mounting" )
525
+
526
+ // Check if any objects with the prefix exist after mounting
527
+ prefixExistsAfter := false
528
+ for _ , obj := range rootListOutputAfter .Contents {
529
+ if strings .HasPrefix (* obj .Key , prefix ) {
530
+ prefixExistsAfter = true
531
+ break
532
+ }
533
+ }
534
+
535
+ if prefixExistsAfter {
536
+ framework .Failf ("Prefix %s was created in bucket %s just by mounting" , prefix , bucketName )
537
+ } else {
538
+ framework .Logf ("Verified prefix %s was not created in bucket %s just by mounting" , prefix , bucketName )
539
+ }
540
+ })
541
+
542
+ // This test verifies that the --prefix mount option correctly isolates
543
+ // objects within a specific prefix in the S3 bucket.
544
+ //
545
+ // Test scenario:
546
+ //
547
+ // [Pod]
548
+ // |
549
+ // ↓
550
+ // [S3 Volume with --prefix=test-prefix/]
551
+ // |
552
+ // ↓
553
+ // [Files stored under test-prefix/ in S3]
554
+ //
555
+ // Expected results:
556
+ // - Files created in the mounted volume are stored under the specified prefix in S3
557
+ // - The files can be accessed through the mounted path without the prefix in the path
558
+ // - No objects are created at the root of the bucket (outside the prefix)
559
+ //
560
+ // This validates that the S3 CSI driver correctly handles the --prefix mount option
561
+ // to properly isolate multiple users or applications within a single bucket.
562
+ ginkgo .It ("should store files under specified prefix when using --prefix option" , func (ctx context.Context ) {
563
+ // We need to access the S3 client directly to verify objects
564
+ s3Client := s3client .New ("" , "" , "" ) // Default credentials/region
565
+
566
+ // Create volume with standard non-root options plus prefix option
567
+ prefix := "test-prefix/"
568
+ resource := BuildVolumeWithOptions (
569
+ ctx ,
570
+ l .config ,
571
+ pattern ,
572
+ DefaultNonRootUser ,
573
+ DefaultNonRootGroup ,
574
+ "" , // No specific file mode
575
+ fmt .Sprintf ("prefix=%s" , prefix ), // Add the prefix option
576
+ )
577
+ l .resources = append (l .resources , resource )
578
+
579
+ // Extract the bucket name from the volume for verification
580
+ bucketName := GetBucketNameFromVolumeResource (resource )
581
+ if bucketName == "" {
582
+ framework .Failf ("Failed to extract bucket name from volume resource" )
583
+ }
584
+
585
+ // Create pod with the prefixed volume
586
+ ginkgo .By ("Creating pod with a volume using prefix option" )
587
+ pod := MakeNonRootPodWithVolume (f .Namespace .Name , []* v1.PersistentVolumeClaim {resource .Pvc }, "" )
588
+ var err error
589
+ pod , err = createPod (ctx , f .ClientSet , f .Namespace .Name , pod )
590
+ framework .ExpectNoError (err )
591
+ defer func () {
592
+ framework .ExpectNoError (e2epod .DeletePodWithWait (ctx , f .ClientSet , pod ))
593
+ }()
594
+
595
+ volPath := "/mnt/volume1"
596
+ testFileName := "prefix-test.txt"
597
+ fileInVol := fmt .Sprintf ("%s/%s" , volPath , testFileName )
598
+ testContent := "Testing prefix mount option"
599
+
600
+ // Write a file to the volume
601
+ ginkgo .By ("Writing a file to the volume" )
602
+ WriteAndVerifyFile (f , pod , fileInVol , testContent )
603
+
604
+ // Verify file can be read from the pod
605
+ ginkgo .By ("Verifying file can be read from pod" )
606
+ e2evolume .VerifyExecInPodSucceed (f , pod , fmt .Sprintf ("cat %s | grep -q '%s'" , fileInVol , testContent ))
607
+
608
+ // List objects in the bucket to verify the object was created under the prefix
609
+ ginkgo .By (fmt .Sprintf ("Verifying object exists under prefix %s in bucket" , prefix ))
610
+
611
+ prefixListOutput , err := s3Client .ListObjectsWithPrefix (ctx , bucketName , prefix )
612
+ framework .ExpectNoError (err , "Failed to list objects with prefix" )
613
+
614
+ // Verify object exists under the prefix
615
+ foundObjectUnderPrefix := false
616
+ expectedKey := prefix + testFileName
617
+ for _ , obj := range prefixListOutput .Contents {
618
+ if * obj .Key == expectedKey {
619
+ foundObjectUnderPrefix = true
620
+ break
621
+ }
622
+ }
623
+ if ! foundObjectUnderPrefix {
624
+ framework .Failf ("Object %s not found under prefix %s in bucket %s" , testFileName , prefix , bucketName )
625
+ } else {
626
+ framework .Logf ("Successfully found object %s under prefix %s in bucket %s" , testFileName , prefix , bucketName )
627
+ }
628
+
629
+ // List objects in the bucket without the prefix to verify no objects exist at the root
630
+ ginkgo .By ("Verifying no objects exist at the root of the bucket" )
631
+ rootListOutput , err := s3Client .ListObjects (ctx , bucketName )
632
+ framework .ExpectNoError (err , "Failed to list objects in bucket" )
633
+
634
+ // Verify no objects exist at the root (that don't have the prefix)
635
+ for _ , obj := range rootListOutput .Contents {
636
+ if ! strings .HasPrefix (* obj .Key , prefix ) {
637
+ framework .Failf ("Found unexpected object %s at root of bucket" , * obj .Key )
638
+ }
639
+ }
640
+ framework .Logf ("No unexpected objects found at root of bucket - all objects have the prefix %s" , prefix )
641
+ })
642
+
643
+ // This test verifies that when objects are created directly in S3 under a prefix,
644
+ // they are visible when mounting with that prefix, and new objects created through
645
+ // the mount are also visible when listing the prefix directly from S3.
646
+ //
647
+ // Test scenario:
648
+ //
649
+ // [Direct S3 API] → [Objects under prefix] ← [Mounted Volume]
650
+ //
651
+ // The test specifically:
652
+ // 1. Creates objects directly in S3 under a specific prefix
653
+ // 2. Mounts a volume with that same prefix
654
+ // 3. Verifies the pre-created objects are visible through the mount
655
+ // 4. Creates new objects through the mount
656
+ // 5. Verifies the new objects are visible when listing the prefix via S3 API
657
+ //
658
+ // This validates that the prefix mount option works bidirectionally with objects
659
+ // created both through the S3 API and through the mounted volume.
660
+ ginkgo .It ("should see objects created directly in S3 under prefix and make new objects visible to S3" , func (ctx context.Context ) {
661
+ // We need to access the S3 client directly to create and list objects
662
+ s3Client := s3client .New (s3client .DefaultRegion , "" , "" ) // Using DefaultRegion from s3client
663
+ var err error
664
+
665
+ // Create volume with standard non-root options plus prefix option
666
+ prefix := "test-both-directions/"
667
+ resource := BuildVolumeWithOptions (
668
+ ctx ,
669
+ l .config ,
670
+ pattern ,
671
+ DefaultNonRootUser ,
672
+ DefaultNonRootGroup ,
673
+ "" , // No specific file mode
674
+ fmt .Sprintf ("prefix=%s" , prefix ), // Add the prefix option
675
+ )
676
+ l .resources = append (l .resources , resource )
677
+
678
+ // Extract the bucket name from the volume for direct S3 operations
679
+ bucketName := GetBucketNameFromVolumeResource (resource )
680
+ if bucketName == "" {
681
+ framework .Failf ("Failed to extract bucket name from volume resource" )
682
+ }
683
+
684
+ directFileKeys := []string {
685
+ "direct-file1.txt" ,
686
+ "direct-file2.txt" ,
687
+ "subdir/direct-file3.txt" ,
688
+ }
689
+
690
+ // Create objects directly in S3 under the prefix
691
+ ginkgo .By (fmt .Sprintf ("Creating objects directly in S3 under prefix %s" , prefix ))
692
+ err = s3Client .CreateObjectsInS3 (ctx , bucketName , prefix , directFileKeys )
693
+ framework .ExpectNoError (err , "Failed to create objects directly in S3" )
694
+
695
+ // Verify objects exist in S3
696
+ ginkgo .By (fmt .Sprintf ("Verifying objects exist in S3 under prefix %s" , prefix ))
697
+ err = s3Client .VerifyObjectsExistInS3 (ctx , bucketName , prefix , directFileKeys )
698
+ framework .ExpectNoError (err , "Failed to verify objects exist in S3" )
699
+
700
+ // Create pod with the prefixed volume
701
+ ginkgo .By ("Creating pod with a volume that uses the same prefix" )
702
+ pod := MakeNonRootPodWithVolume (f .Namespace .Name , []* v1.PersistentVolumeClaim {resource .Pvc }, "" )
703
+ pod , err = createPod (ctx , f .ClientSet , f .Namespace .Name , pod )
704
+ framework .ExpectNoError (err )
705
+ defer func () {
706
+ framework .ExpectNoError (e2epod .DeletePodWithWait (ctx , f .ClientSet , pod ))
707
+ }()
708
+
709
+ // Verify the directly created files are visible through the mount
710
+ volPath := "/mnt/volume1"
711
+ ginkgo .By ("Verifying directly created files are visible through the mount" )
712
+
713
+ // Verify files exist in pod using our helper method
714
+ VerifyFilesExistInPod (f , pod , volPath , directFileKeys )
715
+
716
+ // Additional verification for subdirectory
717
+ subdirPath := fmt .Sprintf ("%s/subdir" , volPath )
718
+ e2evolume .VerifyExecInPodSucceed (f , pod , fmt .Sprintf ("test -d %s" , subdirPath ))
719
+
720
+ // Define mount-created file paths (these will be created through the mount)
721
+ mountCreatedFiles := []string {
722
+ "mount-file1.txt" ,
723
+ "mount-file2.txt" ,
724
+ "subdir/mount-file3.txt" ,
725
+ }
726
+
727
+ // Create files in pod using our helper method
728
+ CreateFilesInPod (f , pod , volPath , mountCreatedFiles )
729
+
730
+ // Verify new files are visible via S3 API with the prefix
731
+ ginkgo .By ("Verifying new files are visible via S3 API with the prefix" )
732
+
733
+ // Verify objects exist in S3
734
+ err = s3Client .VerifyObjectsExistInS3 (ctx , bucketName , prefix , mountCreatedFiles )
735
+ framework .ExpectNoError (err , "Failed to verify mount-created objects exist in S3" )
736
+
737
+ // Additional verification that all objects are present (both direct and mount-created)
738
+ allFiles := append ([]string {}, directFileKeys ... )
739
+ allFiles = append (allFiles , mountCreatedFiles ... )
740
+
741
+ prefixListAfter , err := s3Client .ListObjectsWithPrefix (ctx , bucketName , prefix )
742
+ framework .ExpectNoError (err , "Failed to list objects with prefix after creating files through mount" )
743
+
744
+ // We should have all files (direct + mount-created)
745
+ if len (prefixListAfter .Contents ) < len (allFiles ) {
746
+ framework .Failf ("Expected at least %d objects after creating files through mount, but found %d" ,
747
+ len (allFiles ), len (prefixListAfter .Contents ))
748
+ }
749
+
750
+ framework .Logf ("Successfully verified bidirectional visibility between S3 API and mounted volume with prefix" )
751
+ })
440
752
}
0 commit comments