-
Notifications
You must be signed in to change notification settings - Fork 9.2k
HADOOP-18706: S3ABlockOutputStream recovery, and downgrade syncable will call flush rather than no-op. #5771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7854d64
85e580e
d1b6994
0f2ff5b
2a8458e
e481be6
fd736d0
4f41cf1
7b5f48a
3efa795
d02215e
95138e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1369,12 +1369,69 @@ public S3AEncryptionMethods getS3EncryptionAlgorithm() { | |
| File createTmpFileForWrite(String pathStr, long size, | ||
| Configuration conf) throws IOException { | ||
| initLocalDirAllocatorIfNotInitialized(conf); | ||
| Path path = directoryAllocator.getLocalPathForWrite(pathStr, | ||
| size, conf); | ||
| Path path = directoryAllocator.getLocalPathForWrite(pathStr, size, conf); | ||
| File dir = new File(path.getParent().toUri().getPath()); | ||
| String prefix = path.getName(); | ||
| // create a temp file on this directory | ||
| return File.createTempFile(prefix, null, dir); | ||
| return safeCreateTempFile(pathStr, null, dir); | ||
| } | ||
|
|
||
| // TODO remove this method when hadoop upgrades to a newer version of java than 1.8 | ||
| /** | ||
| * Ensure that the temp file prefix and suffix don't exceed the maximum number of characters | ||
| * allowed by the underlying file system. This validation isn't required in Java 9+ since | ||
| * {@link java.io.File#createTempFile(String, String, File)} automatically truncates file names. | ||
| * | ||
| * @param prefix prefix for the temporary file | ||
| * @param suffix suffix for the temporary file | ||
| * @param dir directory to create the temporary file in | ||
| * @return a unique temporary file | ||
| * @throws IOException | ||
| */ | ||
| static File safeCreateTempFile(String prefix, String suffix, File dir) throws IOException | ||
| { | ||
| // avoid validating multiple times. | ||
| // if the jvm running is version 9+ then defer to java.io.File validation implementation | ||
| if(Float.parseFloat(System.getProperty("java.class.version")) >= 53) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should go in org.apache.hadoop.util.Shell; there's already something similar. will need a test somehow. |
||
| return File.createTempFile(prefix, null, dir); | ||
| } | ||
|
|
||
| // if no suffix was defined assume the default | ||
| if(suffix == null) { | ||
| suffix = ".tmp"; | ||
| } | ||
| // Use only the file name from the supplied prefix | ||
| prefix = (new File(prefix)).getName(); | ||
|
|
||
| int prefixLength = prefix.length(); | ||
| int suffixLength = suffix.length(); | ||
| int maxRandomSuffixLen = 19; // Long.toUnsignedString(Long.MAX_VALUE).length() | ||
|
|
||
| String name; | ||
| int nameMax = 255; // unable to access the underlying FS directly, so assume 255 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. make a constant, e.g ASSUMED_MAX_FILENAME |
||
| int excess = prefixLength + maxRandomSuffixLen + suffixLength - nameMax; | ||
|
|
||
| // shorten the prefix length if the file name exceeds 255 chars | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and replace explicit size with "too long" |
||
| if (excess > 0) { | ||
| // Attempt to shorten the prefix length to no less than 3 | ||
| prefixLength = shortenSubName(prefixLength, excess, 3); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, make a constant and use in both places |
||
| prefix = prefix.substring(0, prefixLength); | ||
| } | ||
| // shorten the suffix if the file name still exceeds 255 chars | ||
| excess = prefixLength + maxRandomSuffixLen + suffixLength - nameMax; | ||
| if (excess > 0) { | ||
| // Attempt to shorten the suffix length to no less than 3 | ||
| suffixLength = shortenSubName(suffixLength, excess, 3); | ||
| suffix = suffix.substring(0, suffixLength); | ||
| } | ||
|
|
||
| return File.createTempFile(prefix, suffix, dir); | ||
| } | ||
|
|
||
| private static int shortenSubName(int subNameLength, int excess, int nameMin) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add javadocs. a unit test would be good too and straightforward to add. |
||
| int newLength = Math.max(nameMin, subNameLength - excess); | ||
| if (newLength < subNameLength) { | ||
| return newLength; | ||
| } | ||
| return subNameLength; | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,9 +29,12 @@ | |
| import org.junit.BeforeClass; | ||
| import org.junit.Test; | ||
|
|
||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.net.URI; | ||
| import java.util.Arrays; | ||
| import java.util.Objects; | ||
|
|
||
| import static org.apache.hadoop.fs.StreamCapabilities.ABORTABLE_STREAM; | ||
| import static org.apache.hadoop.fs.s3a.Constants.*; | ||
|
|
@@ -79,6 +82,46 @@ public void testRegularUpload() throws IOException { | |
| verifyUpload("regular", 1024); | ||
| } | ||
|
|
||
| /** | ||
| * Test that the DiskBlock's local file doesn't result in error when the S3 key exceeds the max | ||
| * char limit of the local file system. Currently | ||
| * {@link java.io.File#createTempFile(String, String, File)} is being relied on to handle the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this comment is out of date |
||
| * truncation. | ||
| * @throws IOException | ||
| */ | ||
| @Test | ||
| public void testDiskBlockCreate() throws IOException { | ||
| String s3Key = // 1024 char | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key__very_long_s3_key__very_long_s3_key__very_long_s3_key__" + | ||
| "very_long_s3_key"; | ||
| long blockSize = getFileSystem().getDefaultBlockSize(); | ||
| try (S3ADataBlocks.BlockFactory diskBlockFactory = | ||
| new S3ADataBlocks.DiskBlockFactory(getFileSystem()); | ||
| S3ADataBlocks.DataBlock dataBlock = | ||
| diskBlockFactory.create("spanId", s3Key, 1, blockSize, null); | ||
| ) { | ||
| String tmpDir = getConfiguration().get("hadoop.tmp.dir"); | ||
| boolean created = Arrays.stream( | ||
| Objects.requireNonNull(new File(tmpDir).listFiles())) | ||
| .anyMatch(f -> f.getName().contains("very_long_s3_key")); | ||
| assertTrue(String.format("tmp file should have been created locally in %s", tmpDir), created); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| LOG.info(dataBlock.toString()); // block file name/location can be viewed in failsafe-report | ||
| } | ||
| } | ||
|
|
||
| @Test(expected = IOException.class) | ||
| public void testWriteAfterStreamClose() throws Throwable { | ||
| Path dest = path("testWriteAfterStreamClose"); | ||
|
|
@@ -136,7 +179,7 @@ private void markAndResetDatablock(S3ADataBlocks.BlockFactory factory) | |
| new S3AInstrumentation(new URI("s3a://example")); | ||
| BlockOutputStreamStatistics outstats | ||
| = instrumentation.newOutputStreamStatistics(null); | ||
| S3ADataBlocks.DataBlock block = factory.create(1, BLOCK_SIZE, outstats); | ||
| S3ADataBlocks.DataBlock block = factory.create("spanId", "object/key", 1, BLOCK_SIZE, outstats); | ||
| block.write(dataset, 0, dataset.length); | ||
| S3ADataBlocks.BlockUploadData uploadData = block.startUpload(); | ||
| InputStream stream = uploadData.getUploadStream(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ | |
| import org.apache.hadoop.fs.s3a.statistics.impl.EmptyS3AStatisticsContext; | ||
| import org.apache.hadoop.fs.s3a.test.MinimalWriteOperationHelperCallbacks; | ||
| import org.apache.hadoop.fs.statistics.IOStatisticsContext; | ||
| import org.apache.hadoop.fs.store.audit.AuditSpan; | ||
| import org.apache.hadoop.util.Progressable; | ||
| import org.junit.Before; | ||
| import org.junit.Test; | ||
|
|
@@ -38,7 +39,10 @@ | |
| import static org.apache.hadoop.test.LambdaTestUtils.intercept; | ||
| import static org.mockito.Mockito.doThrow; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.never; | ||
| import static org.mockito.Mockito.spy; | ||
| import static org.mockito.Mockito.times; | ||
| import static org.mockito.Mockito.verify; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| /** | ||
|
|
@@ -59,6 +63,9 @@ private S3ABlockOutputStream.BlockOutputStreamBuilder mockS3ABuilder() { | |
| mock(S3ADataBlocks.BlockFactory.class); | ||
| long blockSize = Constants.DEFAULT_MULTIPART_SIZE; | ||
| WriteOperationHelper oHelper = mock(WriteOperationHelper.class); | ||
| AuditSpan auditSpan = mock(AuditSpan.class); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can just use |
||
| when(auditSpan.getSpanId()).thenReturn("spanId"); | ||
| when(oHelper.getAuditSpan()).thenReturn(auditSpan); | ||
| PutTracker putTracker = mock(PutTracker.class); | ||
| final S3ABlockOutputStream.BlockOutputStreamBuilder builder = | ||
| S3ABlockOutputStream.builder() | ||
|
|
@@ -156,6 +163,7 @@ public void testSyncableUnsupported() throws Exception { | |
| stream = spy(new S3ABlockOutputStream(builder)); | ||
| intercept(UnsupportedOperationException.class, () -> stream.hflush()); | ||
| intercept(UnsupportedOperationException.class, () -> stream.hsync()); | ||
| verify(stream, never()).flush(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -169,8 +177,11 @@ public void testSyncableDowngrade() throws Exception { | |
| builder.withDowngradeSyncableExceptions(true); | ||
| stream = spy(new S3ABlockOutputStream(builder)); | ||
|
|
||
| verify(stream, never()).flush(); | ||
| stream.hflush(); | ||
| verify(stream, times(1)).flush(); | ||
| stream.hsync(); | ||
| verify(stream, times(2)).flush(); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
think I might prefer something more distinguishable from text, e.g "FS" and "BS" so its easier to read in a dir listing