From b4d8aabe6b405c3cc7677fbbf87b2564f541a90e Mon Sep 17 00:00:00 2001 From: Chris Nauroth Date: Tue, 10 Dec 2013 17:56:30 +0000 Subject: [PATCH] HDFS-5594. FileSystem API for ACLs. Contributed by Chris Nauroth. git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/HDFS-4685@1549910 13f79535-47bb-0310-9956-ffa450edef68 --- .../java/org/apache/hadoop/fs/FileSystem.java | 97 +++++- .../apache/hadoop/fs/FilterFileSystem.java | 41 +++ .../org/apache/hadoop/fs/permission/Acl.java | 141 +++++++++ .../apache/hadoop/fs/permission/AclEntry.java | 216 +++++++++++++ .../hadoop/fs/permission/AclEntryScope.java | 42 +++ .../hadoop/fs/permission/AclEntryType.java | 58 ++++ .../hadoop/fs/permission/AclReadFlag.java | 34 ++ .../hadoop/fs/permission/AclStatus.java | 182 +++++++++++ .../hadoop/fs/permission/AclWriteFlag.java | 34 ++ .../apache/hadoop/fs/permission/FsAction.java | 4 +- .../hadoop/fs/viewfs/ChRootedFileSystem.java | 42 +++ .../hadoop/fs/viewfs/ViewFileSystem.java | 53 ++++ .../apache/hadoop/fs/TestHarFileSystem.java | 17 + .../apache/hadoop/fs/permission/TestAcl.java | 291 ++++++++++++++++++ .../fs/viewfs/TestChRootedFileSystem.java | 48 ++- .../viewfs/TestViewFileSystemDelegation.java | 68 ++++ .../hadoop-hdfs/CHANGES-HDFS-4685.txt | 1 + 17 files changed, 1363 insertions(+), 6 deletions(-) create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/Acl.java create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntry.java create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntryScope.java create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntryType.java create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclReadFlag.java create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclStatus.java create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclWriteFlag.java create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/permission/TestAcl.java diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileSystem.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileSystem.java index 5fab3bb6c84..5ddf52a2d0a 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileSystem.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileSystem.java @@ -25,8 +25,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -40,7 +38,6 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.Stack; import java.util.TreeSet; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.logging.Log; @@ -51,6 +48,10 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; import org.apache.hadoop.fs.Options.ChecksumOpt; import org.apache.hadoop.fs.Options.Rename; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclReadFlag; +import org.apache.hadoop.fs.permission.AclStatus; +import org.apache.hadoop.fs.permission.AclWriteFlag; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.io.MultipleIOException; import org.apache.hadoop.io.Text; @@ -2269,6 +2270,96 @@ public abstract class FileSystem extends Configured implements Closeable { + " doesn't support deleteSnapshot"); } + /** + * Modifies ACL entries of files and directories. This method can add new ACL + * entries or modify the permissions on existing ACL entries. All existing + * ACL entries that are not specified in this call are retained without + * changes. (Modifications are merged into the current ACL.) + * + * @param path Path to modify + * @param aclSpec List describing modifications + * @param flags EnumSet containing flags (such as recursive) + * @throws IOException if an ACL could not be modified + */ + public void modifyAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException { + throw new UnsupportedOperationException(getClass().getSimpleName() + + " doesn't support modifyAclEntries"); + } + + /** + * Removes ACL entries from files and directories. Other ACL entries are + * retained. + * + * @param path Path to modify + * @param aclSpec List describing entries to remove + * @param flags EnumSet containing flags (such as recursive) + * @throws IOException if an ACL could not be modified + */ + public void removeAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException { + throw new UnsupportedOperationException(getClass().getSimpleName() + + " doesn't support removeAclEntries"); + } + + /** + * Removes all default ACL entries from files and directories. + * + * @param path Path to modify + * @param flags EnumSet containing flags (such as recursive) + * @throws IOException if an ACL could not be modified + */ + public void removeDefaultAcl(Path path, EnumSet flags) + throws IOException { + throw new UnsupportedOperationException(getClass().getSimpleName() + + " doesn't support removeDefaultAcl"); + } + + /** + * Removes all but the base ACL entries of files and directories. The entries + * for user, group, and others are retained for compatibility with permission + * bits. + * + * @param path Path to modify + * @param flags EnumSet containing flags (such as recursive) + * @throws IOException if an ACL could not be removed + */ + public void removeAcl(Path path, EnumSet flags) + throws IOException { + throw new UnsupportedOperationException(getClass().getSimpleName() + + " doesn't support removeAcl"); + } + + /** + * Fully replaces ACL of files and directories, discarding all existing + * entries. + * + * @param path Path to modify + * @param aclSpec List describing modifications, must include entries + * for user, group, and others for compatibility with permission bits. + * @param flags EnumSet containing flags (such as recursive) + * @throws IOException if an ACL could not be modified + */ + public void setAcl(Path path, List aclSpec, + EnumSet flags) throws IOException { + throw new UnsupportedOperationException(getClass().getSimpleName() + + " doesn't support setAcl"); + } + + /** + * Gets the ACLs of files and directories. + * + * @param path Path to get + * @param flags EnumSet containing flags (such as recursive) + * @return RemoteIterator which returns each AclStatus + * @throws IOException if an ACL could not be read + */ + public RemoteIterator listAclStatus(Path path, + EnumSet flags) throws IOException { + throw new UnsupportedOperationException(getClass().getSimpleName() + + " doesn't support listAclStatus"); + } + // making it volatile to be able to do a double checked locking private volatile static boolean FILE_SYSTEMS_LOADED = false; diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FilterFileSystem.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FilterFileSystem.java index 3f7b7ed5877..fe2040bfe8b 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FilterFileSystem.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FilterFileSystem.java @@ -22,9 +22,14 @@ import java.io.*; import java.net.URI; import java.net.URISyntaxException; import java.util.EnumSet; +import java.util.List; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclReadFlag; +import org.apache.hadoop.fs.permission.AclStatus; +import org.apache.hadoop.fs.permission.AclWriteFlag; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.ContentSummary; import org.apache.hadoop.fs.Options.ChecksumOpt; @@ -507,4 +512,40 @@ public class FilterFileSystem extends FileSystem { throws IOException { fs.deleteSnapshot(path, snapshotName); } + + @Override + public void modifyAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException { + fs.modifyAclEntries(path, aclSpec, flags); + } + + @Override + public void removeAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException { + fs.removeAclEntries(path, aclSpec, flags); + } + + @Override + public void removeDefaultAcl(Path path, EnumSet flags) + throws IOException { + fs.removeDefaultAcl(path, flags); + } + + @Override + public void removeAcl(Path path, EnumSet flags) + throws IOException { + fs.removeAcl(path, flags); + } + + @Override + public void setAcl(Path path, List aclSpec, + EnumSet flags) throws IOException { + fs.setAcl(path, aclSpec, flags); + } + + @Override + public RemoteIterator listAclStatus(Path path, + EnumSet flags) throws IOException { + return fs.listAclStatus(path, flags); + } } diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/Acl.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/Acl.java new file mode 100644 index 00000000000..a998b40608e --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/Acl.java @@ -0,0 +1,141 @@ +/** + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.permission; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.google.common.base.Objects; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * Defines an Access Control List, which is a set of rules for enforcement of + * permissions on a file or directory. An Acl contains a set of multiple + * {@link AclEntry} instances. The ACL entries define the permissions enforced + * for different classes of users: owner, named user, owning group, named group + * and others. The Acl also contains additional flags associated with the file, + * such as the sticky bit. Acl instances are immutable. Use a {@link Builder} + * to create a new instance. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public class Acl { + private final List entries; + private final boolean stickyBit; + + /** + * Returns the sticky bit. + * + * @return boolean sticky bit + */ + public boolean getStickyBit() { + return stickyBit; + } + + /** + * Returns the list of all ACL entries, ordered by their natural ordering. + * The list is unmodifiable. + * + * @return List unmodifiable ordered list of all ACL entries + */ + public List getEntries() { + return entries; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (getClass() != o.getClass()) { + return false; + } + Acl other = (Acl)o; + return Objects.equal(entries, other.entries) && + Objects.equal(stickyBit, other.stickyBit); + } + + @Override + public int hashCode() { + return Objects.hashCode(entries, stickyBit); + } + + @Override + public String toString() { + return new StringBuilder() + .append("entries: ").append(entries) + .append(", stickyBit: ").append(stickyBit) + .toString(); + } + + /** + * Builder for creating new Acl instances. + */ + public static class Builder { + private List entries = new ArrayList(); + private boolean stickyBit = false; + + /** + * Adds an ACL entry. + * + * @param entry AclEntry entry to add + * @return Builder this builder, for call chaining + */ + public Builder addEntry(AclEntry entry) { + entries.add(entry); + return this; + } + + /** + * Sets sticky bit. If this method is not called, then the builder assumes + * false. + * + * @param stickyBit boolean sticky bit + * @return Builder this builder, for call chaining + */ + public Builder setStickyBit(boolean stickyBit) { + this.stickyBit = stickyBit; + return this; + } + + /** + * Builds a new Acl populated with the set properties. + * + * @return Acl new Acl + */ + public Acl build() { + return new Acl(entries, stickyBit); + } + } + + /** + * Private constructor. + * + * @param entries List list of all ACL entries + * @param boolean sticky bit + */ + private Acl(List entries, boolean stickyBit) { + List entriesCopy = new ArrayList(entries); + Collections.sort(entriesCopy); + this.entries = Collections.unmodifiableList(entriesCopy); + this.stickyBit = stickyBit; + } +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntry.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntry.java new file mode 100644 index 00000000000..2cb2250ccbc --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntry.java @@ -0,0 +1,216 @@ +/** + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.permission; + +import static org.apache.hadoop.fs.permission.AclEntryScope.*; +import static org.apache.hadoop.fs.permission.AclEntryType.*; + +import com.google.common.base.Objects; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * Defines a single entry in an ACL. An ACL entry has a type (user, group, + * mask, or other), an optional name (referring to a specific user or group), a + * set of permissions (any combination of read, write and execute), and a scope + * (access or default). The natural ordering for entries within an ACL is: + *
    + *
  1. owner entry (unnamed user)
  2. + *
  3. all named user entries (internal ordering undefined)
  4. + *
  5. owning group entry (unnamed group)
  6. + *
  7. all named group entries (internal ordering undefined)
  8. + *
  9. other entry
  10. + *
+ * All access ACL entries sort ahead of all default ACL entries. AclEntry + * instances are immutable. Use a {@link Builder} to create a new instance. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public class AclEntry implements Comparable { + private final AclEntryType type; + private final String name; + private final FsAction permission; + private final AclEntryScope scope; + + /** + * Returns the ACL entry type. + * + * @return AclEntryType ACL entry type + */ + public AclEntryType getType() { + return type; + } + + /** + * Returns the optional ACL entry name. + * + * @return String ACL entry name, or null if undefined + */ + public String getName() { + return name; + } + + /** + * Returns the set of permissions in the ACL entry. + * + * @return FsAction set of permissions in the ACL entry + */ + public FsAction getPermission() { + return permission; + } + + /** + * Returns the scope of the ACL entry. + * + * @return AclEntryScope scope of the ACL entry + */ + public AclEntryScope getScope() { + return scope; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (getClass() != o.getClass()) { + return false; + } + AclEntry other = (AclEntry)o; + return Objects.equal(type, other.type) && + Objects.equal(name, other.name) && + Objects.equal(permission, other.permission) && + Objects.equal(scope, other.scope); + } + + @Override + public int hashCode() { + return Objects.hashCode(type, name, permission, scope); + } + + @Override + public int compareTo(AclEntry other) { + return ComparisonChain.start() + .compare(scope, other.scope, Ordering.explicit(ACCESS, DEFAULT)) + .compare(type, other.type, Ordering.explicit(USER, GROUP, MASK, OTHER)) + .compare(name, other.name, Ordering.natural().nullsFirst()) + .result(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (scope == AclEntryScope.DEFAULT) { + sb.append("default:"); + } + if (type != null) { + sb.append(type.toString().toLowerCase()); + } + sb.append(':'); + if (name != null) { + sb.append(name); + } + sb.append(':'); + if (permission != null) { + sb.append(permission.SYMBOL); + } + return sb.toString(); + } + + /** + * Builder for creating new AclEntry instances. + */ + public static class Builder { + private AclEntryType type; + private String name; + private FsAction permission; + private AclEntryScope scope = AclEntryScope.ACCESS; + + /** + * Sets the ACL entry type. + * + * @param type AclEntryType ACL entry type + * @return Builder this builder, for call chaining + */ + public Builder setType(AclEntryType type) { + this.type = type; + return this; + } + + /** + * Sets the optional ACL entry name. + * + * @param name String optional ACL entry name + * @return Builder this builder, for call chaining + */ + public Builder setName(String name) { + this.name = name; + return this; + } + + /** + * Sets the set of permissions in the ACL entry. + * + * @param permission FsAction set of permissions in the ACL entry + * @return Builder this builder, for call chaining + */ + public Builder setPermission(FsAction permission) { + this.permission = permission; + return this; + } + + /** + * Sets the scope of the ACL entry. If this method is not called, then the + * builder assumes {@link AclEntryScope#ACCESS}. + * + * @param scope AclEntryScope scope of the ACL entry + * @return Builder this builder, for call chaining + */ + public Builder setScope(AclEntryScope scope) { + this.scope = scope; + return this; + } + + /** + * Builds a new AclEntry populated with the set properties. + * + * @return AclEntry new AclEntry + */ + public AclEntry build() { + return new AclEntry(type, name, permission, scope); + } + } + + /** + * Private constructor. + * + * @param type AclEntryType ACL entry type + * @param name String optional ACL entry name + * @param permission FsAction set of permissions in the ACL entry + * @param scope AclEntryScope scope of the ACL entry + */ + private AclEntry(AclEntryType type, String name, FsAction permission, AclEntryScope scope) { + this.type = type; + this.name = name; + this.permission = permission; + this.scope = scope; + } +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntryScope.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntryScope.java new file mode 100644 index 00000000000..087a9566e33 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntryScope.java @@ -0,0 +1,42 @@ +/** + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.permission; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * Specifies the scope or intended usage of an ACL entry. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public enum AclEntryScope { + /** + * An ACL entry that is inspected during permission checks to enforce + * permissions. + */ + ACCESS, + + /** + * An ACL entry to be applied to a directory's children that do not otherwise + * have their own ACL defined. Unlike an access ACL entry, a default ACL + * entry is not inspected as part of permission enforcement on the directory + * that owns it. + */ + DEFAULT +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntryType.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntryType.java new file mode 100644 index 00000000000..a9bc3e0786a --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntryType.java @@ -0,0 +1,58 @@ +/** + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.permission; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * Specifies the type of an ACL entry. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public enum AclEntryType { + /** + * An ACL entry applied to a specific user. These ACL entries can be unnamed, + * which applies to the file owner, or named, which applies to the specific + * named user. + */ + USER, + + /** + * An ACL entry applied to a specific group. These ACL entries can be + * unnamed, which applies to the file's group, or named, which applies to the + * specific named group. + */ + GROUP, + + /** + * An ACL mask entry. Mask entries are unnamed. During permission checks, + * the mask entry interacts with all ACL entries that are members of the group + * class. This consists of all named user entries, the unnamed group entry, + * and all named group entries. For each such entry, any permissions that are + * absent from the mask entry are removed from the effective permissions used + * during the permission check. + */ + MASK, + + /** + * An ACL entry that applies to all other users that were not covered by one + * of the more specific ACL entry types. + */ + OTHER +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclReadFlag.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclReadFlag.java new file mode 100644 index 00000000000..0058e95cef5 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclReadFlag.java @@ -0,0 +1,34 @@ +/** + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.permission; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * Flags that control behavior of operations for reading ACL information. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public enum AclReadFlag { + /** + * Read ACLs for all files and directories recursively in the sub-tree of the + * specified path. + */ + RECURSIVE +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclStatus.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclStatus.java new file mode 100644 index 00000000000..cad959984af --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclStatus.java @@ -0,0 +1,182 @@ +/** + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.permission; + +import com.google.common.base.Objects; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.fs.Path; + +/** + * An AclStatus represents an association of a specific file {@link Path} with + * an {@link Acl}. AclStatus instances are immutable. Use a {@link Builder} to + * create a new instance. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public class AclStatus { + private final Path file; + private final String owner; + private final String group; + private final Acl acl; + + /** + * Returns the file associated to this ACL. + * + * @return Path file associated to this ACL + */ + public Path getFile() { + return file; + } + + /** + * Returns the file owner. + * + * @return String file owner + */ + public String getOwner() { + return owner; + } + + /** + * Returns the file group. + * + * @return String file group + */ + public String getGroup() { + return group; + } + + /** + * Returns the ACL. + * + * @return Acl the ACL + */ + public Acl getAcl() { + return acl; + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (getClass() != o.getClass()) { + return false; + } + AclStatus other = (AclStatus)o; + return Objects.equal(file, other.file) && + Objects.equal(owner, other.owner) && + Objects.equal(group, other.group) && + Objects.equal(acl, other.acl); + } + + @Override + public int hashCode() { + return Objects.hashCode(file, owner, group, acl); + } + + @Override + public String toString() { + return new StringBuilder() + .append("file: ").append(file) + .append(", owner: ").append(owner) + .append(", group: ").append(group) + .append(", acl: {").append(acl).append('}') + .toString(); + } + + /** + * Builder for creating new Acl instances. + */ + public static class Builder { + private Path file; + private String owner; + private String group; + private Acl acl; + + /** + * Sets the file associated to this ACL. + * + * @param file Path file associated to this ACL + * @return Builder this builder, for call chaining + */ + public Builder setFile(Path file) { + this.file = file; + return this; + } + + /** + * Sets the file owner. + * + * @param owner String file owner + * @return Builder this builder, for call chaining + */ + public Builder setOwner(String owner) { + this.owner = owner; + return this; + } + + /** + * Sets the file group. + * + * @param group String file group + * @return Builder this builder, for call chaining + */ + public Builder setGroup(String group) { + this.group = group; + return this; + } + + /** + * Sets the ACL. + * + * @param acl Acl the ACL + * @return Builder this builder, for call chaining + */ + public Builder setAcl(Acl acl) { + this.acl = acl; + return this; + } + + /** + * Builds a new Acl populated with the set properties. + * + * @return Acl new Acl + */ + public AclStatus build() { + return new AclStatus(file, owner, group, acl); + } + } + + /** + * Private constructor. + * + * @param file Path file associated to this ACL + * @param owner String file owner + * @param group String file group + * @param acl Acl the ACL + */ + private AclStatus(Path file, String owner, String group, Acl acl) { + this.file = file; + this.owner = owner; + this.group = group; + this.acl = acl; + } +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclWriteFlag.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclWriteFlag.java new file mode 100644 index 00000000000..24741039450 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclWriteFlag.java @@ -0,0 +1,34 @@ +/** + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.permission; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + * Flags that control behavior of operations for writing ACL information. + */ +@InterfaceAudience.Public +@InterfaceStability.Evolving +public enum AclWriteFlag { + /** + * Modify ACLs for all files and directories recursively in the sub-tree of + * the specified path. + */ + RECURSIVE +} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/FsAction.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/FsAction.java index 6fac6472434..7d494145839 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/FsAction.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/FsAction.java @@ -23,8 +23,8 @@ import org.apache.hadoop.classification.InterfaceStability; /** * File system actions, e.g. read, write, etc. */ -@InterfaceAudience.LimitedPrivate({"HDFS"}) -@InterfaceStability.Unstable +@InterfaceAudience.Public +@InterfaceStability.Stable public enum FsAction { // POSIX style NONE("---"), diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java index b73d3c65195..5131cbdceca 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ChRootedFileSystem.java @@ -20,6 +20,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import java.util.EnumSet; +import java.util.List; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; @@ -36,6 +37,11 @@ import org.apache.hadoop.fs.FilterFileSystem; import org.apache.hadoop.fs.FsServerDefaults; import org.apache.hadoop.fs.FsStatus; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.RemoteIterator; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclReadFlag; +import org.apache.hadoop.fs.permission.AclStatus; +import org.apache.hadoop.fs.permission.AclWriteFlag; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.util.Progressable; @@ -278,6 +284,42 @@ class ChRootedFileSystem extends FilterFileSystem { super.setTimes(fullPath(f), mtime, atime); } + @Override + public void modifyAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException { + super.modifyAclEntries(fullPath(path), aclSpec, flags); + } + + @Override + public void removeAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException { + super.removeAclEntries(fullPath(path), aclSpec, flags); + } + + @Override + public void removeDefaultAcl(Path path, EnumSet flags) + throws IOException { + super.removeDefaultAcl(fullPath(path), flags); + } + + @Override + public void removeAcl(Path path, EnumSet flags) + throws IOException { + super.removeAcl(fullPath(path), flags); + } + + @Override + public void setAcl(Path path, List aclSpec, + EnumSet flags) throws IOException { + super.setAcl(fullPath(path), aclSpec, flags); + } + + @Override + public RemoteIterator listAclStatus(Path path, + EnumSet flags) throws IOException { + return super.listAclStatus(fullPath(path), flags); + } + @Override public Path resolvePath(final Path p) throws IOException { return super.resolvePath(fullPath(p)); diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java index aec87a34c04..5b7f7dead92 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/viewfs/ViewFileSystem.java @@ -47,7 +47,12 @@ import org.apache.hadoop.fs.FsConstants; import org.apache.hadoop.fs.FsServerDefaults; import org.apache.hadoop.fs.InvalidPathException; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.RemoteIterator; import org.apache.hadoop.fs.UnsupportedFileSystemException; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclReadFlag; +import org.apache.hadoop.fs.permission.AclStatus; +import org.apache.hadoop.fs.permission.AclWriteFlag; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.fs.viewfs.InodeTree.INode; import org.apache.hadoop.fs.viewfs.InodeTree.INodeLink; @@ -473,6 +478,54 @@ public class ViewFileSystem extends FileSystem { res.targetFileSystem.setTimes(res.remainingPath, mtime, atime); } + @Override + public void modifyAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(path), true); + res.targetFileSystem.modifyAclEntries(res.remainingPath, aclSpec, flags); + } + + @Override + public void removeAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(path), true); + res.targetFileSystem.removeAclEntries(res.remainingPath, aclSpec, flags); + } + + @Override + public void removeDefaultAcl(Path path, EnumSet flags) + throws IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(path), true); + res.targetFileSystem.removeDefaultAcl(res.remainingPath, flags); + } + + @Override + public void removeAcl(Path path, EnumSet flags) + throws IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(path), true); + res.targetFileSystem.removeAcl(res.remainingPath, flags); + } + + @Override + public void setAcl(Path path, List aclSpec, + EnumSet flags) throws IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(path), true); + res.targetFileSystem.setAcl(res.remainingPath, aclSpec, flags); + } + + @Override + public RemoteIterator listAclStatus(Path path, + EnumSet flags) throws IOException { + InodeTree.ResolveResult res = + fsState.resolve(getUriPath(path), true); + return res.targetFileSystem.listAclStatus(res.remainingPath, flags); + } + @Override public void setVerifyChecksum(final boolean verifyChecksum) { List> mountPoints = diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestHarFileSystem.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestHarFileSystem.java index a9c0da8d4e1..11756064cb8 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestHarFileSystem.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestHarFileSystem.java @@ -21,6 +21,10 @@ package org.apache.hadoop.fs; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclReadFlag; +import org.apache.hadoop.fs.permission.AclStatus; +import org.apache.hadoop.fs.permission.AclWriteFlag; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.security.Credentials; import org.apache.hadoop.security.token.Token; @@ -33,6 +37,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.EnumSet; import java.util.Iterator; +import java.util.List; import static org.apache.hadoop.fs.Options.ChecksumOpt; import static org.apache.hadoop.fs.Options.CreateOpts; @@ -165,6 +170,18 @@ public class TestHarFileSystem { String snapshotNewName) throws IOException; public void deleteSnapshot(Path path, String snapshotName) throws IOException; + public void modifyAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException; + public void removeAclEntries(Path path, List aclSpec, + EnumSet flags) throws IOException; + public void removeDefaultAcl(Path path, EnumSet flags) + throws IOException; + public void removeAcl(Path path, EnumSet flags) + throws IOException; + public void setAcl(Path path, List aclSpec, + EnumSet flags) throws IOException; + public RemoteIterator listAclStatus(Path path, + EnumSet flags) throws IOException; } @Test diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/permission/TestAcl.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/permission/TestAcl.java new file mode 100644 index 00000000000..035322ef3dd --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/permission/TestAcl.java @@ -0,0 +1,291 @@ +/** + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.permission; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +import org.apache.hadoop.fs.Path; + +/** + * Tests covering basic functionality of the ACL objects. + */ +public class TestAcl { + private static final Acl ACL1, ACL2, ACL3, ACL4; + private static final AclEntry ENTRY1, ENTRY2, ENTRY3, ENTRY4, ENTRY5, ENTRY6, + ENTRY7, ENTRY8, ENTRY9, ENTRY10, ENTRY11, ENTRY12, ENTRY13; + private static final AclStatus STATUS1, STATUS2, STATUS3; + + static { + // named user + AclEntry.Builder aclEntryBuilder = new AclEntry.Builder() + .setType(AclEntryType.USER) + .setName("user1") + .setPermission(FsAction.ALL); + ENTRY1 = aclEntryBuilder.build(); + ENTRY2 = aclEntryBuilder.build(); + // named group + ENTRY3 = new AclEntry.Builder() + .setType(AclEntryType.GROUP) + .setName("group2") + .setPermission(FsAction.READ_WRITE) + .build(); + // default other + ENTRY4 = new AclEntry.Builder() + .setType(AclEntryType.OTHER) + .setPermission(FsAction.NONE) + .setScope(AclEntryScope.DEFAULT) + .build(); + // owner + ENTRY5 = new AclEntry.Builder() + .setType(AclEntryType.USER) + .setPermission(FsAction.ALL) + .build(); + // default named group + ENTRY6 = new AclEntry.Builder() + .setType(AclEntryType.GROUP) + .setName("group3") + .setPermission(FsAction.READ_WRITE) + .setScope(AclEntryScope.DEFAULT) + .build(); + // other + ENTRY7 = new AclEntry.Builder() + .setType(AclEntryType.OTHER) + .setPermission(FsAction.NONE) + .build(); + // default named user + ENTRY8 = new AclEntry.Builder() + .setType(AclEntryType.USER) + .setName("user3") + .setPermission(FsAction.ALL) + .setScope(AclEntryScope.DEFAULT) + .build(); + // mask + ENTRY9 = new AclEntry.Builder() + .setType(AclEntryType.MASK) + .setPermission(FsAction.READ) + .build(); + // default mask + ENTRY10 = new AclEntry.Builder() + .setType(AclEntryType.MASK) + .setPermission(FsAction.READ_EXECUTE) + .setScope(AclEntryScope.DEFAULT) + .build(); + // group + ENTRY11 = new AclEntry.Builder() + .setType(AclEntryType.GROUP) + .setPermission(FsAction.READ) + .build(); + // default group + ENTRY12 = new AclEntry.Builder() + .setType(AclEntryType.GROUP) + .setPermission(FsAction.READ) + .setScope(AclEntryScope.DEFAULT) + .build(); + // default owner + ENTRY13 = new AclEntry.Builder() + .setType(AclEntryType.USER) + .setPermission(FsAction.ALL) + .setScope(AclEntryScope.DEFAULT) + .build(); + + Acl.Builder aclBuilder = new Acl.Builder() + .addEntry(ENTRY1) + .addEntry(ENTRY3) + .addEntry(ENTRY4); + ACL1 = aclBuilder.build(); + ACL2 = aclBuilder.build(); + ACL3 = new Acl.Builder() + .setStickyBit(true) + .build(); + + AclStatus.Builder aclStatusBuilder = new AclStatus.Builder() + .setFile(new Path("file1")) + .setOwner("owner1") + .setGroup("group1") + .setAcl(ACL1); + STATUS1 = aclStatusBuilder.build(); + STATUS2 = aclStatusBuilder.build(); + STATUS3 = new AclStatus.Builder() + .setFile(new Path("file2")) + .setOwner("owner2") + .setGroup("group2") + .setAcl(ACL3) + .build(); + + ACL4 = new Acl.Builder() + .addEntry(ENTRY1) + .addEntry(ENTRY3) + .addEntry(ENTRY4) + .addEntry(ENTRY5) + .addEntry(ENTRY6) + .addEntry(ENTRY7) + .addEntry(ENTRY8) + .addEntry(ENTRY9) + .addEntry(ENTRY10) + .addEntry(ENTRY11) + .addEntry(ENTRY12) + .addEntry(ENTRY13) + .build(); + } + + @Test + public void testAclEquals() { + assertNotSame(ACL1, ACL2); + assertNotSame(ACL1, ACL3); + assertNotSame(ACL2, ACL3); + assertEquals(ACL1, ACL1); + assertEquals(ACL2, ACL2); + assertEquals(ACL1, ACL2); + assertEquals(ACL2, ACL1); + assertFalse(ACL1.equals(ACL3)); + assertFalse(ACL2.equals(ACL3)); + assertFalse(ACL1.equals(null)); + assertFalse(ACL1.equals(new Object())); + } + + @Test + public void testAclHashCode() { + assertEquals(ACL1.hashCode(), ACL2.hashCode()); + assertFalse(ACL1.hashCode() == ACL3.hashCode()); + } + + @Test + public void testAclEntriesImmutable() { + AclEntry entry = new AclEntry.Builder().build(); + List entries = ACL1.getEntries(); + try { + entries.add(entry); + fail("expected adding ACL entry to fail"); + } catch (UnsupportedOperationException e) { + // expected + } + } + + @Test + public void testEntryEquals() { + assertNotSame(ENTRY1, ENTRY2); + assertNotSame(ENTRY1, ENTRY3); + assertNotSame(ENTRY1, ENTRY4); + assertNotSame(ENTRY2, ENTRY3); + assertNotSame(ENTRY2, ENTRY4); + assertNotSame(ENTRY3, ENTRY4); + assertEquals(ENTRY1, ENTRY1); + assertEquals(ENTRY2, ENTRY2); + assertEquals(ENTRY1, ENTRY2); + assertEquals(ENTRY2, ENTRY1); + assertFalse(ENTRY1.equals(ENTRY3)); + assertFalse(ENTRY1.equals(ENTRY4)); + assertFalse(ENTRY3.equals(ENTRY4)); + assertFalse(ENTRY1.equals(null)); + assertFalse(ENTRY1.equals(new Object())); + } + + @Test + public void testEntryHashCode() { + assertEquals(ENTRY1.hashCode(), ENTRY2.hashCode()); + assertFalse(ENTRY1.hashCode() == ENTRY3.hashCode()); + assertFalse(ENTRY1.hashCode() == ENTRY4.hashCode()); + assertFalse(ENTRY3.hashCode() == ENTRY4.hashCode()); + } + + @Test + public void testEntryNaturalOrdering() { + AclEntry expected[] = new AclEntry[] { + ENTRY5, // owner + ENTRY1, // named user + ENTRY11, // group + ENTRY3, // named group + ENTRY9, // mask + ENTRY7, // other + ENTRY13, // default owner + ENTRY8, // default named user + ENTRY12, // default group + ENTRY6, // default named group + ENTRY10, // default mask + ENTRY4 // default other + }; + List actual = ACL4.getEntries(); + assertNotNull(actual); + assertEquals(expected.length, actual.size()); + for (int i = 0; i < expected.length; ++i) { + AclEntry expectedEntry = expected[i]; + AclEntry actualEntry = actual.get(i); + assertEquals( + String.format("At position %d, expected = %s, actual = %s", i, + expectedEntry, actualEntry), + expectedEntry, actualEntry); + } + } + + @Test + public void testEntryScopeIsAccessIfUnspecified() { + assertEquals(AclEntryScope.ACCESS, ENTRY1.getScope()); + assertEquals(AclEntryScope.ACCESS, ENTRY2.getScope()); + assertEquals(AclEntryScope.ACCESS, ENTRY3.getScope()); + assertEquals(AclEntryScope.DEFAULT, ENTRY4.getScope()); + } + + @Test + public void testStatusEquals() { + assertNotSame(STATUS1, STATUS2); + assertNotSame(STATUS1, STATUS3); + assertNotSame(STATUS2, STATUS3); + assertEquals(STATUS1, STATUS1); + assertEquals(STATUS2, STATUS2); + assertEquals(STATUS1, STATUS2); + assertEquals(STATUS2, STATUS1); + assertFalse(STATUS1.equals(STATUS3)); + assertFalse(STATUS2.equals(STATUS3)); + assertFalse(STATUS1.equals(null)); + assertFalse(STATUS1.equals(new Object())); + } + + @Test + public void testStatusHashCode() { + assertEquals(STATUS1.hashCode(), STATUS2.hashCode()); + assertFalse(STATUS1.hashCode() == STATUS3.hashCode()); + } + + @Test + public void testToString() { + assertEquals( + "entries: [user:user1:rwx, group:group2:rw-, default:other::---], stickyBit: false", + ACL1.toString()); + assertEquals( + "entries: [user:user1:rwx, group:group2:rw-, default:other::---], stickyBit: false", + ACL2.toString()); + assertEquals("entries: [], stickyBit: true", ACL3.toString()); + assertEquals("user:user1:rwx", ENTRY1.toString()); + assertEquals("user:user1:rwx", ENTRY2.toString()); + assertEquals("group:group2:rw-", ENTRY3.toString()); + assertEquals("default:other::---", ENTRY4.toString()); + assertEquals( + "file: file1, owner: owner1, group: group1, acl: {entries: [user:user1:rwx, group:group2:rw-, default:other::---], stickyBit: false}", + STATUS1.toString()); + assertEquals( + "file: file1, owner: owner1, group: group1, acl: {entries: [user:user1:rwx, group:group2:rw-, default:other::---], stickyBit: false}", + STATUS2.toString()); + assertEquals( + "file: file2, owner: owner2, group: group2, acl: {entries: [], stickyBit: true}", + STATUS3.toString()); + } +} diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestChRootedFileSystem.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestChRootedFileSystem.java index 8454025ba3d..e3b1b8b37d3 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestChRootedFileSystem.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestChRootedFileSystem.java @@ -20,6 +20,9 @@ package org.apache.hadoop.fs.viewfs; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; @@ -29,6 +32,9 @@ import org.apache.hadoop.fs.FileSystemTestHelper; import org.apache.hadoop.fs.FilterFileSystem; import org.apache.hadoop.fs.FsConstants; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclReadFlag; +import org.apache.hadoop.fs.permission.AclWriteFlag; import org.apache.hadoop.fs.viewfs.ChRootedFileSystem; import org.junit.After; import org.junit.Assert; @@ -354,6 +360,46 @@ public class TestChRootedFileSystem { new ChRootedFileSystem(chrootUri, conf); } + /** + * Tests that ChRootedFileSystem delegates calls for every ACL method to the + * underlying FileSystem with all Path arguments translated as required to + * enforce chroot. + */ + @Test + public void testAclMethodsPathTranslation() throws IOException { + Configuration conf = new Configuration(); + conf.setClass("fs.mockfs.impl", MockFileSystem.class, FileSystem.class); + + URI chrootUri = URI.create("mockfs://foo/a/b"); + ChRootedFileSystem chrootFs = new ChRootedFileSystem(chrootUri, conf); + FileSystem mockFs = ((FilterFileSystem)chrootFs.getRawFileSystem()) + .getRawFileSystem(); + + Path chrootPath = new Path("/c"); + Path rawPath = new Path("/a/b/c"); + List entries = Collections.emptyList(); + EnumSet writeFlags = EnumSet.noneOf(AclWriteFlag.class); + EnumSet readFlags = EnumSet.noneOf(AclReadFlag.class); + + chrootFs.modifyAclEntries(chrootPath, entries, writeFlags); + verify(mockFs).modifyAclEntries(rawPath, entries, writeFlags); + + chrootFs.removeAclEntries(chrootPath, entries, writeFlags); + verify(mockFs).removeAclEntries(rawPath, entries, writeFlags); + + chrootFs.removeDefaultAcl(chrootPath, writeFlags); + verify(mockFs).removeDefaultAcl(rawPath, writeFlags); + + chrootFs.removeAcl(chrootPath, writeFlags); + verify(mockFs).removeAcl(rawPath, writeFlags); + + chrootFs.setAcl(chrootPath, entries, writeFlags); + verify(mockFs).setAcl(rawPath, entries, writeFlags); + + chrootFs.listAclStatus(chrootPath, readFlags); + verify(mockFs).listAclStatus(rawPath, readFlags); + } + static class MockFileSystem extends FilterFileSystem { MockFileSystem() { super(mock(FileSystem.class)); @@ -361,4 +407,4 @@ public class TestChRootedFileSystem { @Override public void initialize(URI name, Configuration conf) throws IOException {} } -} \ No newline at end of file +} diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestViewFileSystemDelegation.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestViewFileSystemDelegation.java index 487ce91a078..69477b97a99 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestViewFileSystemDelegation.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/viewfs/TestViewFileSystemDelegation.java @@ -20,14 +20,22 @@ package org.apache.hadoop.fs.viewfs; import java.io.IOException; import java.net.URI; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FileSystemTestHelper; import org.apache.hadoop.fs.FsConstants; import org.apache.hadoop.fs.LocalFileSystem; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclReadFlag; +import org.apache.hadoop.fs.permission.AclWriteFlag; +import org.apache.hadoop.fs.viewfs.TestChRootedFileSystem.MockFileSystem; import org.junit.*; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** * Verify that viewfs propagates certain methods to the underlying fs @@ -57,6 +65,15 @@ public class TestViewFileSystemDelegation { //extends ViewFileSystemTestSetup { return fs; } + private static FileSystem setupMockFileSystem(Configuration conf, URI uri) + throws Exception { + String scheme = uri.getScheme(); + conf.set("fs." + scheme + ".impl", MockFileSystem.class.getName()); + FileSystem fs = FileSystem.get(uri, conf); + ConfigUtil.addLink(conf, "/mounts/" + scheme, uri); + return ((MockFileSystem)fs).getRawFileSystem(); + } + @Test public void testSanity() { assertEquals("fs1:/", fs1.getUri().toString()); @@ -69,6 +86,57 @@ public class TestViewFileSystemDelegation { //extends ViewFileSystemTestSetup { checkVerifyChecksum(true); } + /** + * Tests that ViewFileSystem dispatches calls for every ACL method through the + * mount table to the correct underlying FileSystem with all Path arguments + * translated as required. + */ + @Test + public void testAclMethods() throws Exception { + Configuration conf = ViewFileSystemTestSetup.createConfig(); + FileSystem mockFs1 = setupMockFileSystem(conf, new URI("mockfs1:/")); + FileSystem mockFs2 = setupMockFileSystem(conf, new URI("mockfs2:/")); + FileSystem viewFs = FileSystem.get(FsConstants.VIEWFS_URI, conf); + + Path viewFsPath1 = new Path("/mounts/mockfs1/a/b/c"); + Path mockFsPath1 = new Path("/a/b/c"); + Path viewFsPath2 = new Path("/mounts/mockfs2/d/e/f"); + Path mockFsPath2 = new Path("/d/e/f"); + List entries = Collections.emptyList(); + EnumSet writeFlags = EnumSet.noneOf(AclWriteFlag.class); + EnumSet readFlags = EnumSet.noneOf(AclReadFlag.class); + + viewFs.modifyAclEntries(viewFsPath1, entries, writeFlags); + verify(mockFs1).modifyAclEntries(mockFsPath1, entries, writeFlags); + viewFs.modifyAclEntries(viewFsPath2, entries, writeFlags); + verify(mockFs2).modifyAclEntries(mockFsPath2, entries, writeFlags); + + viewFs.removeAclEntries(viewFsPath1, entries, writeFlags); + verify(mockFs1).removeAclEntries(mockFsPath1, entries, writeFlags); + viewFs.removeAclEntries(viewFsPath2, entries, writeFlags); + verify(mockFs2).removeAclEntries(mockFsPath2, entries, writeFlags); + + viewFs.removeDefaultAcl(viewFsPath1, writeFlags); + verify(mockFs1).removeDefaultAcl(mockFsPath1, writeFlags); + viewFs.removeDefaultAcl(viewFsPath2, writeFlags); + verify(mockFs2).removeDefaultAcl(mockFsPath2, writeFlags); + + viewFs.removeAcl(viewFsPath1, writeFlags); + verify(mockFs1).removeAcl(mockFsPath1, writeFlags); + viewFs.removeAcl(viewFsPath2, writeFlags); + verify(mockFs2).removeAcl(mockFsPath2, writeFlags); + + viewFs.setAcl(viewFsPath1, entries, writeFlags); + verify(mockFs1).setAcl(mockFsPath1, entries, writeFlags); + viewFs.setAcl(viewFsPath2, entries, writeFlags); + verify(mockFs2).setAcl(mockFsPath2, entries, writeFlags); + + viewFs.listAclStatus(viewFsPath1, readFlags); + verify(mockFs1).listAclStatus(mockFsPath1, readFlags); + viewFs.listAclStatus(viewFsPath2, readFlags); + verify(mockFs2).listAclStatus(mockFsPath2, readFlags); + } + void checkVerifyChecksum(boolean flag) { viewFs.setVerifyChecksum(flag); assertEquals(flag, fs1.getVerifyChecksum()); diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt index 14552edfdbc..8b04e7b368e 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt +++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt @@ -7,6 +7,7 @@ HDFS-4685 (Unreleased) NEW FEATURES IMPROVEMENTS + HDFS-5594. FileSystem API for ACLs. (cnauroth) OPTIMIZATIONS