4242import org .slf4j .LoggerFactory ;
4343
4444import org .apache .hadoop .conf .Configuration ;
45- import org .apache .hadoop .fs .FileStatus ;
4645import org .apache .hadoop .fs .FileSystem ;
4746import org .apache .hadoop .fs .Path ;
4847import org .apache .hadoop .fs .contract .ContractTestUtils ;
5857import static org .apache .hadoop .fs .s3a .S3ATestUtils .*;
5958import static org .apache .hadoop .fs .s3a .S3AUtils .applyLocatedFiles ;
6059import static org .apache .hadoop .fs .s3a .Statistic .FILES_DELETE_REJECTED ;
60+ import static org .apache .hadoop .fs .s3a .Statistic .OBJECT_DELETE_OBJECTS ;
6161import static org .apache .hadoop .fs .s3a .Statistic .OBJECT_DELETE_REQUESTS ;
6262import static org .apache .hadoop .fs .s3a .auth .RoleModel .Effects ;
6363import static org .apache .hadoop .fs .s3a .auth .RoleModel .Statement ;
@@ -333,7 +333,8 @@ protected Configuration createConfiguration() {
333333 MAX_THREADS ,
334334 MAXIMUM_CONNECTIONS ,
335335 S3GUARD_DDB_BACKGROUND_SLEEP_MSEC_KEY ,
336- DIRECTORY_MARKER_POLICY );
336+ DIRECTORY_MARKER_POLICY ,
337+ BULK_DELETE_PAGE_SIZE );
337338 conf .setInt (MAX_THREADS , EXECUTOR_THREAD_COUNT );
338339 conf .setInt (MAXIMUM_CONNECTIONS , EXECUTOR_THREAD_COUNT * 2 );
339340 // turn off prune delays, so as to stop scale tests creating
@@ -342,6 +343,10 @@ protected Configuration createConfiguration() {
342343 // use the keep policy to ensure that surplus markers exist
343344 // to complicate failures
344345 conf .set (DIRECTORY_MARKER_POLICY , DIRECTORY_MARKER_POLICY_KEEP );
346+ // set the delete page size to its maximum to ensure that all
347+ // entries are included in the same large delete, even on
348+ // scale runs. This is needed for assertions on the result.
349+ conf .setInt (BULK_DELETE_PAGE_SIZE , 1_000 );
345350 return conf ;
346351 }
347352
@@ -482,8 +487,11 @@ public void testRenameDirFailsInDelete() throws Throwable {
482487
483488 // create a set of files
484489 // this is done in parallel as it is 10x faster on a long-haul test run.
485- List <Path > createdFiles = createFiles (fs , readOnlyDir , dirDepth , fileCount ,
486- dirCount );
490+ List <Path > dirs = new ArrayList <>(dirCount );
491+ List <Path > createdFiles = createDirsAndFiles (fs , readOnlyDir , dirDepth ,
492+ fileCount , dirCount ,
493+ new ArrayList <>(fileCount ),
494+ dirs );
487495 // are they all there?
488496 int expectedFileCount = createdFiles .size ();
489497 assertFileCount ("files ready to rename" , roleFS ,
@@ -500,26 +508,36 @@ public void testRenameDirFailsInDelete() throws Throwable {
500508 MultiObjectDeleteException .class , deniedException );
501509 final List <Path > undeleted
502510 = extractUndeletedPaths (mde , fs ::keyToQualifiedPath );
511+
512+ List <Path > expectedUndeletedFiles = new ArrayList <>(createdFiles );
513+ if (getFileSystem ().getDirectoryMarkerPolicy ()
514+ .keepDirectoryMarkers (readOnlyDir )) {
515+ // directory markers are being retained,
516+ // so will also be in the list of undeleted files
517+ expectedUndeletedFiles .addAll (dirs );
518+ }
503519 Assertions .assertThat (undeleted )
504520 .as ("files which could not be deleted" )
505- .hasSize (expectedFileCount )
506- .containsAll (createdFiles )
507- .containsExactlyInAnyOrderElementsOf (createdFiles );
521+ .containsExactlyInAnyOrderElementsOf (expectedUndeletedFiles );
508522 }
509523 LOG .info ("Result of renaming read-only files is as expected" ,
510524 deniedException );
511525 assertFileCount ("files in the source directory" , roleFS ,
512526 readOnlyDir , expectedFileCount );
513527 // now lets look at the destination.
514- // even with S3Guard on, we expect the destination to match that of our
528+ // even with S3Guard on, we expect the destination to match that of
515529 // the remote state.
516530 // the test will exist
517531 describe ("Verify destination directory exists" );
518- FileStatus st = roleFS .getFileStatus (writableDir );
519- assertTrue ("Not a directory: " + st ,
520- st .isDirectory ());
532+ assertIsDirectory (writableDir );
521533 assertFileCount ("files in the dest directory" , roleFS ,
522534 writableDir , expectedFileCount );
535+ // all directories in the source tree must still exist,
536+ // which for S3Guard means no tombstone markers were added
537+ LOG .info ("Verifying all directories still exist" );
538+ for (Path dir : dirs ) {
539+ assertIsDirectory (dir );
540+ }
523541 }
524542
525543 @ Test
@@ -618,7 +636,13 @@ public void testPartialDirDelete() throws Throwable {
618636 S3AFileSystem fs = getFileSystem ();
619637 StoreContext storeContext = fs .createStoreContext ();
620638
621- List <Path > readOnlyFiles = createFiles (fs , readOnlyDir ,
639+ List <Path > dirs = new ArrayList <>(dirCount );
640+ List <Path > readOnlyFiles = createDirsAndFiles (
641+ fs , readOnlyDir , dirDepth ,
642+ fileCount , dirCount ,
643+ new ArrayList <>(fileCount ),
644+ dirs );
645+ createFiles (fs , readOnlyDir ,
622646 dirDepth , fileCount , dirCount );
623647 List <Path > deletableFiles = createFiles (fs ,
624648 writableDir , dirDepth , fileCount , dirCount );
@@ -642,16 +666,19 @@ public void testPartialDirDelete() throws Throwable {
642666 // this set can be deleted by the role FS
643667 MetricDiff rejectionCount = new MetricDiff (roleFS , FILES_DELETE_REJECTED );
644668 MetricDiff deleteVerbCount = new MetricDiff (roleFS , OBJECT_DELETE_REQUESTS );
669+ MetricDiff deleteObjectCount = new MetricDiff (roleFS , OBJECT_DELETE_OBJECTS );
645670
646671 describe ("Trying to delete read only directory" );
647672 AccessDeniedException ex = expectDeleteForbidden (readOnlyDir );
648673 if (multiDelete ) {
649674 // multi-delete status checks
650675 extractCause (MultiObjectDeleteException .class , ex );
676+ deleteVerbCount .assertDiffEquals ("Wrong delete request count" , 1 );
677+ deleteObjectCount .assertDiffEquals ("Wrong count of objects in delete request" ,
678+ readOnlyFiles .size ());
651679 rejectionCount .assertDiffEquals ("Wrong rejection count" ,
652680 readOnlyFiles .size ());
653- deleteVerbCount .assertDiffEquals ("Wrong delete count" , 1 );
654- reset (rejectionCount , deleteVerbCount );
681+ reset (rejectionCount , deleteVerbCount , deleteObjectCount );
655682 }
656683 // all the files are still there? (avoid in scale test due to cost)
657684 if (!scaleTest ) {
@@ -669,6 +696,9 @@ public void testPartialDirDelete() throws Throwable {
669696 mde , keyPaths , storeContext ::keyToPath );
670697 final List <Path > undeleted = toPathList (
671698 undeletedKeyPaths );
699+ deleteObjectCount .assertDiffEquals (
700+ "Wrong count of objects in delete request" ,
701+ allFiles .size ());
672702 Assertions .assertThat (undeleted )
673703 .as ("files which could not be deleted" )
674704 .containsExactlyInAnyOrderElementsOf (readOnlyFiles );
@@ -691,7 +721,7 @@ public void testPartialDirDelete() throws Throwable {
691721
692722 Assertions .assertThat (readOnlyListing )
693723 .as ("ReadOnly directory " + directoryList )
694- .containsAll (readOnlyFiles );
724+ .containsExactlyInAnyOrderElementsOf (readOnlyFiles );
695725 }
696726
697727 /**
@@ -785,7 +815,7 @@ private static CompletableFuture<Path> put(FileSystem fs,
785815 }
786816
787817 /**
788- * Parallel-touch a set of files in the destination directory.
818+ * Build a set of files in a directory tree .
789819 * @param fs filesystem
790820 * @param destDir destination
791821 * @param depth file depth
@@ -798,10 +828,31 @@ public static List<Path> createFiles(final FileSystem fs,
798828 final int depth ,
799829 final int fileCount ,
800830 final int dirCount ) throws IOException {
801- List <CompletableFuture <Path >> futures = new ArrayList <>(fileCount );
802- List <Path > paths = new ArrayList <>(fileCount );
803- List <Path > dirs = new ArrayList <>(dirCount );
831+ return createDirsAndFiles (fs , destDir , depth , fileCount , dirCount ,
832+ new ArrayList <Path >(fileCount ),
833+ new ArrayList <Path >(dirCount ));
834+ }
835+
836+ /**
837+ * Build a set of files in a directory tree.
838+ * @param fs filesystem
839+ * @param destDir destination
840+ * @param depth file depth
841+ * @param fileCount number of files to create.
842+ * @param dirCount number of dirs to create at each level
843+ * @param paths [out] list of file paths created
844+ * @param dirs [out] list of directory paths created.
845+ * @return the list of files created.
846+ */
847+ public static List <Path > createDirsAndFiles (final FileSystem fs ,
848+ final Path destDir ,
849+ final int depth ,
850+ final int fileCount ,
851+ final int dirCount ,
852+ final List <Path > paths ,
853+ final List <Path > dirs ) throws IOException {
804854 buildPaths (paths , dirs , destDir , depth , fileCount , dirCount );
855+ List <CompletableFuture <Path >> futures = new ArrayList <>(paths .size () + dirs .size ());
805856
806857 // create directories. With dir marker retention, that adds more entries
807858 // to cause deletion issues
@@ -817,7 +868,7 @@ public static List<Path> createFiles(final FileSystem fs,
817868 }
818869
819870 try (DurationInfo ignore =
820- new DurationInfo (LOG , "Creating %d files" , fileCount )) {
871+ new DurationInfo (LOG , "Creating %d files" , paths . size () )) {
821872 for (Path path : paths ) {
822873 futures .add (put (fs , path , path .getName ()));
823874 }
0 commit comments