-
Notifications
You must be signed in to change notification settings - Fork 629
HDDS-14043. Fix ls -e UnsupportedOperationException on ofs/o3fs #10209
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
fbf88ff
75eb75a
874bcf0
56a8e0c
4634f83
2139690
f3d3423
f4ead6c
c72af40
3182fd6
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 |
|---|---|---|
|
|
@@ -73,6 +73,7 @@ | |
| import org.apache.hadoop.fs.FileAlreadyExistsException; | ||
| import org.apache.hadoop.fs.FileStatus; | ||
| import org.apache.hadoop.fs.FileSystem; | ||
| import org.apache.hadoop.fs.FsShell; | ||
| import org.apache.hadoop.fs.LocatedFileStatus; | ||
| import org.apache.hadoop.fs.Path; | ||
| import org.apache.hadoop.fs.PathFilter; | ||
|
|
@@ -403,6 +404,49 @@ public void testCreateKeyWithECReplicationConfig() throws Exception { | |
| createKeyWithECReplicationConfig(root, cluster.getConf()); | ||
| } | ||
|
|
||
| @Test | ||
| void testContentSummaryErasureCodingPolicy() throws Exception { | ||
| String ratisKey = "ratis-ec-policy-key"; | ||
| String ecKey = "ec-policy-key"; | ||
| ECReplicationConfig ecConfig = new ECReplicationConfig("RS-3-2-1024k"); | ||
| Path parentDir = new Path(OZONE_URI_DELIMITER, "ec-policy-mixed-o3fs"); | ||
| Path ratisFile = new Path(parentDir, ratisKey); | ||
| Path ecFile = new Path(parentDir, ecKey); | ||
|
|
||
| fs.mkdirs(parentDir); | ||
| String ratisRelKey = "ec-policy-mixed-o3fs/" + ratisKey; | ||
| String ecRelKey = "ec-policy-mixed-o3fs/" + ecKey; | ||
| TestDataUtil.createKey(ozoneBucket, ratisRelKey, | ||
| RatisReplicationConfig.getInstance(HddsProtos.ReplicationFactor.THREE), | ||
| new byte[]{0}); | ||
| TestDataUtil.createKey(ozoneBucket, ecRelKey, ecConfig, | ||
| new byte[]{0}); | ||
|
|
||
| try { | ||
| assertEquals("", | ||
| fs.getContentSummary(ROOT).getErasureCodingPolicy()); | ||
| assertEquals("Replicated", | ||
| fs.getContentSummary(ratisFile).getErasureCodingPolicy()); | ||
| assertEquals(ecConfig.getReplication(), | ||
| fs.getContentSummary(ecFile).getErasureCodingPolicy()); | ||
| assertEquals("", | ||
| fs.getContentSummary(parentDir).getErasureCodingPolicy()); | ||
| } finally { | ||
| fs.delete(parentDir, true); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testLsDashEDoesNotThrow() throws Exception { | ||
|
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. Medium — weak regression coverage: Consider capturing Same note applies to
Contributor
Author
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. Thanks for checking this. The two tests intentionally cover separate layers: The CLI test covers the original exception, while |
||
| FsShell shell = new FsShell(fs.getConf()); | ||
| try { | ||
| int exitCode = shell.run(new String[]{"-ls", "-R", "-e", fsRoot}); | ||
| assertEquals(0, exitCode); | ||
| } finally { | ||
| shell.close(); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testDeleteCreatesFakeParentDir() throws Exception { | ||
| deleteCreatesFakeParentDir(ROOT); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,6 +51,7 @@ | |
| import java.util.stream.Collectors; | ||
| import org.apache.hadoop.conf.Configuration; | ||
| import org.apache.hadoop.fs.BlockLocation; | ||
| import org.apache.hadoop.fs.ContentSummary; | ||
| import org.apache.hadoop.fs.CreateFlag; | ||
| import org.apache.hadoop.fs.FSDataInputStream; | ||
| import org.apache.hadoop.fs.FSDataOutputStream; | ||
|
|
@@ -865,6 +866,91 @@ public BlockLocation[] getFileBlockLocations(FileStatus fileStatus, | |
| } | ||
| } | ||
|
|
||
| @Override | ||
| public ContentSummary getContentSummary(Path f) throws IOException { | ||
|
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. Low — maintenance risk:
Contributor
Author
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. Agreed that the two implementations have a similar shape and may carry some maintenance risk. However, they use different filesystem-specific paths, adapters, listing, and tracing flows, so extracting a shared helper would broaden this focused fix. I’d prefer to keep this PR scoped and revisit the duplication if these implementations need to change again. 🙏 |
||
| Path qualifiedPath = f.makeQualified(uri, workingDir); | ||
| String key = pathToKey(qualifiedPath); | ||
| FileStatusAdapter status; | ||
| try { | ||
| status = adapter.getFileStatus(key, uri, qualifiedPath, getUsername()); | ||
| } catch (OMException ex) { | ||
| if (ex.getResult().equals(OMException.ResultCodes.KEY_NOT_FOUND)) { | ||
| throw new FileNotFoundException("File not found. path:" + f); | ||
| } | ||
| throw ex; | ||
| } | ||
|
|
||
| if (status.isFile()) { | ||
| long length = status.getLength(); | ||
| long spaceConsumed = status.getDiskConsumed(); | ||
| ContentSummary.Builder builder = new ContentSummary.Builder().length(length). | ||
| fileCount(1).directoryCount(0).spaceConsumed(spaceConsumed); | ||
| applyEcPolicy(builder, status.getErasureCodingPolicy()); | ||
| return builder.build(); | ||
| } | ||
|
|
||
| long[] summary = {0, 0, 0, 1}; | ||
| for (FileStatusAdapter s : listStatusAdapter(f)) { | ||
| long length = s.getLength(); | ||
| long spaceConsumed = s.getDiskConsumed(); | ||
| ContentSummary c; | ||
| if (s.isDir()) { | ||
| c = getContentSummary(s.getPath()); | ||
| } else { | ||
| ContentSummary.Builder childBuilder = new ContentSummary.Builder().length(length). | ||
| fileCount(1).directoryCount(0).spaceConsumed(spaceConsumed); | ||
| applyEcPolicy(childBuilder, s.getErasureCodingPolicy()); | ||
| c = childBuilder.build(); | ||
| } | ||
|
|
||
| summary[0] += c.getLength(); | ||
| summary[1] += c.getSpaceConsumed(); | ||
| summary[2] += c.getFileCount(); | ||
| summary[3] += c.getDirectoryCount(); | ||
| } | ||
|
|
||
| ContentSummary.Builder builder = new ContentSummary.Builder().length(summary[0]). | ||
| fileCount(summary[2]).directoryCount(summary[3]). | ||
| spaceConsumed(summary[1]); | ||
| applyEcPolicy(builder, status.getErasureCodingPolicy()); | ||
| return builder.build(); | ||
| } | ||
|
|
||
| /** | ||
| * Apply the erasure coding policy on the {@link ContentSummary.Builder}. | ||
| * Default implementation is a no-op so that this class can compile and run | ||
| * against Hadoop 2, where {@code ContentSummary.Builder.erasureCodingPolicy} | ||
| * does not exist. The Hadoop 3 subclass overrides this to set the policy. | ||
| */ | ||
| protected void applyEcPolicy(ContentSummary.Builder builder, String ecPolicy) { | ||
|
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. Low — Hadoop 2 behavior: Fine if Hadoop 3-only support for
Contributor
Author
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. Yeah, Hadoop 2.10.2 doesn’t support ls -e, and I’ve noted that in the PR description. |
||
| } | ||
|
|
||
| private List<FileStatusAdapter> listStatusAdapter(Path f) throws IOException { | ||
| int numEntries = listingPageSize; | ||
| LinkedList<FileStatusAdapter> statuses = new LinkedList<>(); | ||
| List<FileStatusAdapter> tmpStatusList; | ||
| String startKey = ""; | ||
| int entriesAdded; | ||
| do { | ||
| tmpStatusList = adapter.listStatus(pathToKey(f), false, startKey, | ||
| numEntries, uri, workingDir, getUsername(), true); | ||
| entriesAdded = 0; | ||
| if (!tmpStatusList.isEmpty()) { | ||
| if (startKey.isEmpty() || !statuses.getLast().getPath().toString() | ||
| .equals(tmpStatusList.get(0).getPath().toString())) { | ||
| statuses.addAll(tmpStatusList); | ||
| entriesAdded += tmpStatusList.size(); | ||
| } else { | ||
| statuses.addAll(tmpStatusList.subList(1, tmpStatusList.size())); | ||
| entriesAdded += tmpStatusList.size() - 1; | ||
| } | ||
| startKey = pathToKey(statuses.getLast().getPath()); | ||
| } | ||
| } while (entriesAdded > 0); | ||
|
|
||
| return statuses; | ||
| } | ||
|
|
||
| @Override | ||
| public short getDefaultReplication() { | ||
| return adapter.getDefaultReplication(); | ||
|
|
||
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.
Ideally, ContentSummaryComputationContext.REPLICATED is better than "Replicated"
But wouldn't work with Hadoop 2 so this is fine.