You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by sa...@apache.org on 2023/04/27 05:30:21 UTC
[ozone] branch master updated: HDDS-7828. Make Ozone fs rm symbolic links command support posix behaviour (#4246)
This is an automated email from the ASF dual-hosted git repository.
sammichen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 6a7c0706cb HDDS-7828. Make Ozone fs rm symbolic links command support posix behaviour (#4246)
6a7c0706cb is described below
commit 6a7c0706cb86baad0f375b966e050e8cf4f2a5a3
Author: Neil Joshi <ne...@gmail.com>
AuthorDate: Wed Apr 26 23:30:14 2023 -0600
HDDS-7828. Make Ozone fs rm symbolic links command support posix behaviour (#4246)
---
.../hadoop/fs/ozone/TestRootedOzoneFileSystem.java | 192 ++++++++++++++++++-
.../fs/ozone/BasicRootedOzoneFileSystem.java | 39 +++-
.../org/apache/hadoop/fs/ozone/OzoneFsDelete.java | 206 +++++++++++++++++++++
.../org/apache/hadoop/fs/ozone/OzoneFsShell.java | 10 +
.../hadoop/fs/ozone/TestBasicOzoneFileSystems.java | 12 ++
.../apache/hadoop/fs/ozone/TestOzoneFsShell.java | 68 +++++++
6 files changed, 521 insertions(+), 6 deletions(-)
diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
index 1f8c86ec8c..32f308790e 100644
--- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
+++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
@@ -70,6 +70,7 @@ import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLIdentityType;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
import org.apache.hadoop.ozone.security.acl.OzoneAclConfig;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.util.ToolRunner;
import org.apache.ozone.test.GenericTestUtils;
import org.apache.ozone.test.LambdaTestUtils;
import org.junit.After;
@@ -121,8 +122,9 @@ import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY;
import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ENABLE_OFS_SHARED_TMP_DIR;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_NOT_FOUND;
-import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND;
+import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.KEY_NOT_FOUND;
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.PERMISSION_DENIED;
+import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.WRITE;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.DELETE;
@@ -1521,6 +1523,194 @@ public class TestRootedOzoneFileSystem {
Assert.assertFalse(volumeExist(volumeStr3));
}
+ private void createSymlinkSrcDestPaths(String srcVol,
+ String srcBucket, String destVol, String destBucket) throws IOException {
+ // src srcVol/srcBucket
+ Path volumeSrcPath = new Path(OZONE_URI_DELIMITER + srcVol);
+ Path bucketSrcPath = Path.mergePaths(volumeSrcPath,
+ new Path(OZONE_URI_DELIMITER + srcBucket));
+ fs.mkdirs(volumeSrcPath);
+ OzoneVolume volume = objectStore.getVolume(srcVol);
+ Assert.assertEquals(srcVol, volume.getName());
+ fs.mkdirs(bucketSrcPath);
+ OzoneBucket bucket = volume.getBucket(srcBucket);
+ Assert.assertEquals(srcBucket, bucket.getName());
+
+ // dest link destVol/destBucket -> srcVol/srcBucket
+ Path volumeLinkPath = new Path(OZONE_URI_DELIMITER + destVol);
+ fs.mkdirs(volumeLinkPath);
+ volume = objectStore.getVolume(destVol);
+ Assert.assertEquals(destVol, volume.getName());
+ createLinkBucket(destVol, destBucket, srcVol, srcBucket);
+ }
+
+ @Test
+ public void testSymlinkList() throws Exception {
+ OzoneFsShell shell = new OzoneFsShell(conf);
+ // setup symlink, destVol/destBucket -> srcVol/srcBucket
+ String srcVolume = getRandomNonExistVolumeName();
+ final String srcBucket = "bucket";
+ String destVolume = getRandomNonExistVolumeName();
+ createSymlinkSrcDestPaths(srcVolume, srcBucket, destVolume, srcBucket);
+
+ try {
+ // test symlink -ls -R destVol/destBucket -> srcVol/srcBucket
+ // srcBucket no keys
+ // run toolrunner ozone fs shell commands
+ try (GenericTestUtils.SystemOutCapturer capture =
+ new GenericTestUtils.SystemOutCapturer()) {
+ String linkPathStr = rootPath + destVolume;
+ ToolRunner.run(shell, new String[]{"-ls", "-R", linkPathStr});
+ Assert.assertTrue(capture.getOutput().contains("drwxrwxrwx"));
+ Assert.assertTrue(capture.getOutput().contains(linkPathStr +
+ OZONE_URI_DELIMITER + srcBucket));
+ } finally {
+ shell.close();
+ }
+
+ // add key in source bucket
+ final String key = "object-dir/object-name1";
+ try (OzoneOutputStream outputStream = objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket)
+ .createKey(key, 1)) {
+ outputStream.write(RandomUtils.nextBytes(1));
+ }
+ Assert.assertEquals(objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket).getKey(key).getName(), key);
+
+ // test ls -R /destVol/destBucket, srcBucket with key (non-empty)
+ try (GenericTestUtils.SystemOutCapturer capture =
+ new GenericTestUtils.SystemOutCapturer()) {
+ String linkPathStr = rootPath + destVolume;
+ ToolRunner.run(shell, new String[]{"-ls", "-R",
+ linkPathStr + OZONE_URI_DELIMITER + srcBucket});
+ Assert.assertTrue(capture.getOutput().contains("drwxrwxrwx"));
+ Assert.assertTrue(capture.getOutput().contains(linkPathStr +
+ OZONE_URI_DELIMITER + srcBucket));
+ Assert.assertTrue(capture.getOutput().contains("-rw-rw-rw-"));
+ Assert.assertTrue(capture.getOutput().contains(linkPathStr +
+ OZONE_URI_DELIMITER + srcBucket + OZONE_URI_DELIMITER + key));
+ } finally {
+ shell.close();
+ }
+ } finally {
+ // cleanup; note must delete link before link src bucket
+ // due to bug - HDDS-7884
+ fs.delete(new Path(OZONE_URI_DELIMITER + destVolume +
+ OZONE_URI_DELIMITER + srcBucket));
+ fs.delete(new Path(OZONE_URI_DELIMITER + srcVolume), true);
+ fs.delete(new Path(OZONE_URI_DELIMITER + destVolume), true);
+ }
+ }
+
+ @Test
+ public void testSymlinkPosixDelete() throws Exception {
+ OzoneFsShell shell = new OzoneFsShell(conf);
+ // setup symlink, destVol/destBucket -> srcVol/srcBucket
+ String srcVolume = getRandomNonExistVolumeName();
+ final String srcBucket = "bucket";
+ String destVolume = getRandomNonExistVolumeName();
+ createSymlinkSrcDestPaths(srcVolume, srcBucket, destVolume, srcBucket);
+
+ try {
+ // test symlink destVol/destBucket -> srcVol/srcBucket exists
+ Assert.assertTrue(fs.exists(new Path(OZONE_URI_DELIMITER +
+ destVolume + OZONE_URI_DELIMITER + srcBucket)));
+
+ // add key to srcBucket
+ final String key = "object-dir/object-name1";
+ try (OzoneOutputStream outputStream = objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket)
+ .createKey(key, 1)) {
+ outputStream.write(RandomUtils.nextBytes(1));
+ }
+ Assert.assertEquals(objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket).getKey(key).getName(), key);
+
+ // test symlink -rm destVol/destBucket -> srcVol/srcBucket
+ // should delete only link, srcBucket and key unaltered
+ // run toolrunner ozone fs shell commands
+ try {
+ String linkPathStr = rootPath + destVolume + OZONE_URI_DELIMITER +
+ srcBucket;
+ int res = ToolRunner.run(shell, new String[]{"-rm", "-skipTrash",
+ linkPathStr});
+ Assert.assertEquals(0, res);
+
+ try {
+ objectStore.getVolume(destVolume).getBucket(srcBucket);
+ Assert.fail("Bucket should not exist, should throw OMException");
+ } catch (OMException ex) {
+ Assert.assertEquals(BUCKET_NOT_FOUND, ex.getResult());
+ }
+
+ Assert.assertEquals(srcBucket, objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket).getName());
+ Assert.assertEquals(key, objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket).getKey(key).getName());
+
+ // re-create symlink
+ createLinkBucket(destVolume, srcBucket, srcVolume, srcBucket);
+ Assert.assertTrue(fs.exists(new Path(OZONE_URI_DELIMITER +
+ destVolume + OZONE_URI_DELIMITER + srcBucket)));
+
+ // test symlink -rm -R -f destVol/destBucket/ -> srcVol/srcBucket
+ // should delete only link contents (src bucket contents),
+ // link and srcBucket unaltered
+ // run toolrunner ozone fs shell commands
+ linkPathStr = rootPath + destVolume + OZONE_URI_DELIMITER + srcBucket;
+ res = ToolRunner.run(shell, new String[]{"-rm", "-skipTrash",
+ "-f", "-R", linkPathStr + OZONE_URI_DELIMITER});
+ Assert.assertEquals(0, res);
+
+ Assert.assertEquals(srcBucket, objectStore.getVolume(destVolume)
+ .getBucket(srcBucket).getName());
+ Assert.assertEquals(true, objectStore.getVolume(destVolume)
+ .getBucket(srcBucket).isLink());
+ Assert.assertEquals(srcBucket, objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket).getName());
+ try {
+ objectStore.getVolume(srcVolume).getBucket(srcBucket).getKey(key);
+ Assert.fail("Key should be deleted under srcBucket, " +
+ "OMException expected");
+ } catch (OMException ex) {
+ Assert.assertEquals(KEY_NOT_FOUND, ex.getResult());
+ }
+
+ // test symlink -rm -R -f destVol/destBucket -> srcVol/srcBucket
+ // should delete only link
+ // run toolrunner ozone fs shell commands
+ linkPathStr = rootPath + destVolume + OZONE_URI_DELIMITER + srcBucket;
+ res = ToolRunner.run(shell, new String[]{"-rm", "-skipTrash",
+ "-f", "-R", linkPathStr});
+ Assert.assertEquals(0, res);
+
+ Assert.assertEquals(srcBucket, objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket).getName());
+ // test link existence
+ try {
+ objectStore.getVolume(destVolume).getBucket(srcBucket);
+ Assert.fail("link should not exist, " +
+ "OMException expected");
+ } catch (OMException ex) {
+ Assert.assertEquals(BUCKET_NOT_FOUND, ex.getResult());
+ }
+ // test src bucket existence
+ Assert.assertEquals(objectStore.getVolume(srcVolume)
+ .getBucket(srcBucket).getName(), srcBucket);
+ } finally {
+ shell.close();
+ }
+ } finally {
+ // cleanup; note must delete link before link src bucket
+ // due to bug - HDDS-7884
+ fs.delete(new Path(OZONE_URI_DELIMITER + destVolume + OZONE_URI_DELIMITER
+ + srcBucket));
+ fs.delete(new Path(OZONE_URI_DELIMITER + srcVolume), true);
+ fs.delete(new Path(OZONE_URI_DELIMITER + destVolume), true);
+ }
+ }
+
@Test
public void testDeleteBucketLink() throws Exception {
// Create test volume, bucket, directory
diff --git a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
index b11a40a317..cffb36c144 100644
--- a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
+++ b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
@@ -92,7 +92,7 @@ import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLU
* This is a basic version which doesn't extend
* KeyProviderTokenIssuer and doesn't include statistics. It can be used
* from older hadoop version. For newer hadoop version use the full featured
- * BasicRootedOzoneFileSystem.
+ * RootedOzoneFileSystem.
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
@@ -696,8 +696,13 @@ public class BasicRootedOzoneFileSystem extends FileSystem {
return false;
}
- // remove link bucket directly
- if (isLinkBucket(f, ofsPath)) {
+ // handling posix symlink delete behaviours
+ // i.) rm [-r] <symlink path>, delete symlink not target bucket contents
+ // ii.) rm -r <symlink path>/, delete target bucket contents not symlink
+ boolean handleTrailingSlash = f.toString().endsWith(OZONE_URI_DELIMITER);
+ // remove link bucket directly if link and
+ // rm path does not have trailing slash
+ if (isLinkBucket(f, ofsPath) && !handleTrailingSlash) {
deleteBucketFromVolume(f, ofsPath);
return true;
}
@@ -705,8 +710,12 @@ public class BasicRootedOzoneFileSystem extends FileSystem {
// delete inner content of bucket
boolean result = innerDelete(f, recursive);
- // Handle delete bucket
- deleteBucketFromVolume(f, ofsPath);
+ // check if rm path does not have trailing slash
+ // if so, the contents of bucket were deleted and skip delete bucket
+ // otherwise, Handle delete bucket
+ if (!handleTrailingSlash) {
+ deleteBucketFromVolume(f, ofsPath);
+ }
return result;
}
@@ -1473,6 +1482,26 @@ public class BasicRootedOzoneFileSystem extends FileSystem {
spaceConsumed(summary[1]).build();
}
+ @Override
+ public boolean supportsSymlinks() {
+ return true;
+ }
+
+ @Override
+ public Path getLinkTarget(Path f) throws IOException {
+ OFSPath ofsPath = new OFSPath(f,
+ OzoneConfiguration.of(getConfSource()));
+ if (ofsPath.isBucket()) { // only support bucket links
+ OzoneBucket bucket = adapterImpl.getBucket(ofsPath, false);
+ if (bucket.isLink()) {
+ return new Path(OZONE_URI_DELIMITER +
+ bucket.getSourceVolume() + OZONE_URI_DELIMITER +
+ bucket.getSourceBucket());
+ }
+ }
+ return f;
+ }
+
public SnapshotDiffReport getSnapshotDiffReport(final Path snapshotDir,
final String fromSnapshot, final String toSnapshot)
throws IOException, InterruptedException {
diff --git a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFsDelete.java b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFsDelete.java
new file mode 100644
index 0000000000..672d822030
--- /dev/null
+++ b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFsDelete.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.fs.ozone;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URI;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.fs.ContentSummary;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.PathIOException;
+import org.apache.hadoop.fs.PathIsDirectoryException;
+import org.apache.hadoop.fs.PathNotFoundException;
+import org.apache.hadoop.fs.Trash;
+import org.apache.hadoop.fs.shell.CommandFactory;
+import org.apache.hadoop.fs.shell.CommandFormat;
+import org.apache.hadoop.fs.shell.FsCommand;
+import org.apache.hadoop.fs.shell.PathData;
+import org.apache.hadoop.util.ToolRunner;
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SHELL_SAFELY_DELETE_LIMIT_NUM_FILES;
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SHELL_SAFELY_DELETE_LIMIT_NUM_FILES_DEFAULT;
+import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER;
+
+/**
+ * Classes that delete paths.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+
+public final class OzoneFsDelete {
+
+ private OzoneFsDelete() {
+ }
+
+ public static void registerCommands(CommandFactory factory) {
+ factory.addClass(OzoneFsDelete.Rm.class, "-rm");
+ factory.addClass(OzoneFsDelete.Rmr.class, "-rmr");
+ }
+
+ /** remove non-directory paths. */
+ public static class Rm extends FsCommand {
+ public static final String NAME = "rm";
+ public static final String USAGE = "[-f] [-r|-R] [-skipTrash] " +
+ "[-safely] <src> ...";
+ public static final String DESCRIPTION =
+ "Delete all files that match the specified file pattern. " +
+ "Equivalent to the Unix command \"rm <src>\"\n" +
+ "-f: If the file does not exist, do not display a diagnostic " +
+ "message or modify the exit status to reflect an error.\n" +
+ "-[rR]: Recursively deletes directories.\n" +
+ "-skipTrash: option bypasses trash, if enabled, and immediately " +
+ "deletes <src>.\n" +
+ "-safely: option requires safety confirmation, if enabled, " +
+ "requires confirmation before deleting large directory with more " +
+ "than <hadoop.shell.delete.limit.num.files> files. Delay is " +
+ "expected when walking over large directory recursively to count " +
+ "the number of files to be deleted before the confirmation.\n";
+
+ private boolean skipTrash = false;
+ private boolean deleteDirs = false;
+ private boolean ignoreFNF = false;
+ private boolean safeDelete = false;
+ private boolean trailing = false;
+
+ @Override
+ protected void processOptions(LinkedList<String> args) throws IOException {
+ CommandFormat cf = new CommandFormat(
+ 1, Integer.MAX_VALUE, "f", "r", "R", "skipTrash", "safely");
+ cf.parse(args);
+ ignoreFNF = cf.getOpt("f");
+ deleteDirs = cf.getOpt("r") || cf.getOpt("R");
+ skipTrash = cf.getOpt("skipTrash");
+ safeDelete = cf.getOpt("safely");
+ }
+
+ @Override
+ protected List<PathData> expandArgument(String arg) throws IOException {
+ try {
+ // handle trailing slash for symlinks
+ if (arg.endsWith(OZONE_URI_DELIMITER)) {
+ trailing = true;
+ }
+ return super.expandArgument(arg);
+ } catch (PathNotFoundException e) {
+ if (!ignoreFNF) {
+ throw e;
+ }
+ // prevent -f on a non-existent glob from failing
+ return new LinkedList<PathData>();
+ }
+ }
+
+ @Override
+ protected void processNonexistentPath(PathData item) throws IOException {
+ if (!ignoreFNF) {
+ super.processNonexistentPath(item);
+ }
+ }
+
+ @Override
+ protected void processPath(PathData item) throws IOException {
+ boolean isSymlink = false;
+ if (item.fs.supportsSymlinks()) {
+ isSymlink = item.fs.getLinkTarget(item.path) != item.path;
+ }
+ // support posix rm of symlink in addition to rm -r for directories
+ if (item.stat.isDirectory() && !deleteDirs && !isSymlink) {
+ throw new PathIsDirectoryException(item.toString());
+ }
+ // support posix symlink delete with trailing slash 'rm <path symlink>/'
+ // deletes contents of symlink bucket and retains symlink
+ Path path = item.path;
+ if (isSymlink && trailing) {
+ path = new Path(URI.create(path.toString() + OZONE_URI_DELIMITER));
+ }
+ // Any problem (ie. creating the trash dir,
+ // moving the item to be deleted, etc), has the trash
+ // service throw exceptions. User can retry correcting
+ // the problem.
+ if (moveToTrash(item) || !canBeSafelyDeleted(item)) {
+ return;
+ }
+ if (!item.fs.delete(path, deleteDirs)) {
+ throw new PathIOException(item.toString());
+ }
+ out.println("Deleted " + item + (trailing ? OZONE_URI_DELIMITER : ""));
+ }
+
+ private boolean canBeSafelyDeleted(PathData item)
+ throws IOException {
+ boolean shouldDelete = true;
+ if (safeDelete) {
+ final long deleteLimit = getConf().getLong(
+ HADOOP_SHELL_SAFELY_DELETE_LIMIT_NUM_FILES,
+ HADOOP_SHELL_SAFELY_DELETE_LIMIT_NUM_FILES_DEFAULT);
+ if (deleteLimit > 0) {
+ ContentSummary cs = item.fs.getContentSummary(item.path);
+ final long numFiles = cs.getFileCount();
+ if (numFiles > deleteLimit) {
+ if (!ToolRunner.confirmPrompt("Proceed deleting " + numFiles +
+ " files?")) {
+ System.err.println("Delete aborted at user request.\n");
+ shouldDelete = false;
+ }
+ }
+ }
+ }
+ return shouldDelete;
+ }
+
+ private boolean moveToTrash(PathData item) throws IOException {
+ boolean success = false;
+ if (!skipTrash) {
+ try {
+ success = Trash.moveToAppropriateTrash(item.fs, item.path, getConf());
+ } catch (FileNotFoundException fnfe) {
+ throw fnfe;
+ } catch (IOException ioe) {
+ String msg = ioe.getMessage();
+ if (ioe.getCause() != null) {
+ msg += ": " + ioe.getCause().getMessage();
+ }
+ throw new IOException(msg + ". Consider using -skipTrash option",
+ ioe);
+ }
+ }
+ return success;
+ }
+ }
+
+ /** remove any path. */
+ static class Rmr extends OzoneFsDelete.Rm {
+ public static final String NAME = "rmr";
+
+ @Override
+ protected void processOptions(LinkedList<String> args) throws IOException {
+ args.addFirst("-r");
+ super.processOptions(args);
+ }
+
+ @Override
+ public String getReplacementCommand() {
+ return "-rm -r";
+ }
+ }
+
+}
diff --git a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFsShell.java b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFsShell.java
index 4ff4916b61..3e494c1c37 100644
--- a/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFsShell.java
+++ b/hadoop-ozone/ozonefs-common/src/main/java/org/apache/hadoop/fs/ozone/OzoneFsShell.java
@@ -17,6 +17,7 @@
*/
package org.apache.hadoop.fs.ozone;
+import com.google.common.annotations.VisibleForTesting;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FsShell;
import org.apache.hadoop.fs.shell.CommandFactory;
@@ -60,6 +61,8 @@ public class OzoneFsShell extends FsShell {
// commands, and then this method can be abstract
if (this.getClass().equals(OzoneFsShell.class)) {
factory.registerCommands(FsCommand.class);
+ // ozone delete rm command registration supersedes fs delete
+ factory.registerCommands(OzoneFsDelete.class);
}
}
@@ -97,4 +100,11 @@ public class OzoneFsShell extends FsShell {
protected static OzoneFsShell newShellInstance() {
return new OzoneFsShell();
}
+
+ // for testing purposes, ensure that ozone specific
+ // added fs commands are visible
+ @VisibleForTesting
+ public CommandFactory getCommandFactory() {
+ return commandFactory;
+ }
}
diff --git a/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicOzoneFileSystems.java b/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicOzoneFileSystems.java
index 1db1ee5b4e..0414fe52cb 100644
--- a/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicOzoneFileSystems.java
+++ b/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestBasicOzoneFileSystems.java
@@ -22,6 +22,7 @@ import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.conf.StorageSize;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -72,6 +73,16 @@ public class TestBasicOzoneFileSystems {
assertDefaultBlockSize(toBytes(customValue));
}
+ // test for filesystem pseduo-posix symlink support
+ @Test
+ public void testFileSystemPosixSymlinkSupport() {
+ if (subject.getClass() == BasicRootedOzoneFileSystem.class) {
+ Assert.assertTrue(subject.supportsSymlinks());
+ } else {
+ Assert.assertFalse(subject.supportsSymlinks());
+ }
+ }
+
private void assertDefaultBlockSize(long expected) {
assertEquals(expected, subject.getDefaultBlockSize());
@@ -86,4 +97,5 @@ public class TestBasicOzoneFileSystems {
StorageSize blockSize = StorageSize.parse(value);
return (long) blockSize.getUnit().toBytes(blockSize.getValue());
}
+
}
diff --git a/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsShell.java b/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsShell.java
new file mode 100644
index 0000000000..6974369bba
--- /dev/null
+++ b/hadoop-ozone/ozonefs-common/src/test/java/org/apache/hadoop/fs/ozone/TestOzoneFsShell.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hadoop.fs.ozone;
+
+import org.apache.hadoop.fs.shell.Command;
+import org.apache.hadoop.fs.shell.CommandFactory;
+import org.apache.hadoop.util.ToolRunner;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Arrays;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+
+/**
+ * Tests the behavior of OzoneFsShell.
+ */
+public class TestOzoneFsShell {
+
+ // tests command handler for FsShell bound to OzoneDelete class
+ @Test
+ public void testOzoneFsShellRegisterDeleteCmd() throws IOException {
+ final String rmCmdName = "rm";
+ final String rmCmd = "-" + rmCmdName;
+ final String arg = "arg1";
+ OzoneFsShell shell = new OzoneFsShell();
+ String[] argv = {arg, arg};
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ PrintStream bytesPrintStream = new PrintStream(bytes, false, UTF_8.name());
+ PrintStream oldErr = System.err;
+ System.setErr(bytesPrintStream);
+ try {
+ ToolRunner.run(shell, argv);
+ } catch (Exception e) {
+ } finally {
+ // test command bindings for "rm" command handled by OzoneDelete class
+ CommandFactory factory = shell.getCommandFactory();
+ Assert.assertEquals(1, Arrays.stream(factory.getNames())
+ .filter(c -> c.equals(rmCmd)).count());
+ Command instance = factory.getInstance(rmCmd);
+ Assert.assertNotNull(instance);
+ Assert.assertEquals(OzoneFsDelete.Rm.class, instance.getClass());
+ Assert.assertEquals(rmCmdName, instance.getCommandName());
+ shell.close();
+ System.setErr(oldErr);
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org