diff --git a/CHANGES.txt b/CHANGES.txt index c1c8ce76de..98405edf6f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -29,6 +29,8 @@ Trunk (unreleased changes) HADOOP-6920. Metrics instrumentation to move new metrics2 framework. (Luke Lu via suresh) + HADOOP-7214. Add Common functionality necessary to provide an equivalent + of /usr/bin/groups for Hadoop. (Aaron T. Myers via todd) IMPROVEMENTS diff --git a/src/java/org/apache/hadoop/tools/GetGroupsBase.java b/src/java/org/apache/hadoop/tools/GetGroupsBase.java new file mode 100644 index 0000000000..4d627cbb5f --- /dev/null +++ b/src/java/org/apache/hadoop/tools/GetGroupsBase.java @@ -0,0 +1,107 @@ +/** + * 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.tools; + +import java.io.IOException; +import java.io.PrintStream; +import java.net.InetSocketAddress; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.conf.Configured; +import org.apache.hadoop.ipc.RPC; +import org.apache.hadoop.net.NetUtils; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.util.Tool; + +/** + * Base class for the HDFS and MR implementations of tools which fetch and + * display the groups that users belong to. + */ +public abstract class GetGroupsBase extends Configured implements Tool { + + private PrintStream out; + + /** + * Create an instance of this tool using the given configuration. + * @param conf + */ + protected GetGroupsBase(Configuration conf) { + this(conf, System.out); + } + + /** + * Used exclusively for testing. + * + * @param conf The configuration to use. + * @param out The PrintStream to write to, instead of System.out + */ + protected GetGroupsBase(Configuration conf, PrintStream out) { + super(conf); + this.out = out; + } + + /** + * Get the groups for the users given and print formatted output to the + * {@link PrintStream} configured earlier. + */ + @Override + public int run(String[] args) throws Exception { + if (args.length == 0) { + args = new String[] { UserGroupInformation.getCurrentUser().getUserName() }; + } + + for (String username : args) { + StringBuilder sb = new StringBuilder(); + sb.append(username + " :"); + for (String group : getUgmProtocol().getGroupsForUser(username)) { + sb.append(" "); + sb.append(group); + } + out.println(sb); + } + + return 0; + } + + /** + * Must be overridden by subclasses to get the address where the + * {@link GetUserMappingsProtocol} implementation is running. + * + * @param conf The configuration to use. + * @return The address where the service is listening. + * @throws IOException + */ + protected abstract InetSocketAddress getProtocolAddress(Configuration conf) + throws IOException; + + /** + * Get a client of the {@link GetUserMappingsProtocol}. + * @return A {@link GetUserMappingsProtocol} client proxy. + * @throws IOException + */ + private GetUserMappingsProtocol getUgmProtocol() throws IOException { + GetUserMappingsProtocol userGroupMappingProtocol = + RPC.getProxy(GetUserMappingsProtocol.class, + GetUserMappingsProtocol.versionID, + getProtocolAddress(getConf()), UserGroupInformation.getCurrentUser(), + getConf(), NetUtils.getSocketFactory(getConf(), + GetUserMappingsProtocol.class)); + return userGroupMappingProtocol; + } + +} diff --git a/src/java/org/apache/hadoop/tools/GetUserMappingsProtocol.java b/src/java/org/apache/hadoop/tools/GetUserMappingsProtocol.java new file mode 100644 index 0000000000..0b48304340 --- /dev/null +++ b/src/java/org/apache/hadoop/tools/GetUserMappingsProtocol.java @@ -0,0 +1,46 @@ +/** + * 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.tools; + +import java.io.IOException; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; +import org.apache.hadoop.ipc.VersionedProtocol; + +/** + * Protocol implemented by the Name Node and Job Tracker which maps users to + * groups. + */ +@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) +@InterfaceStability.Evolving +public interface GetUserMappingsProtocol extends VersionedProtocol { + + /** + * Version 1: Initial version. + */ + public static final long versionID = 1L; + + /** + * Get the groups which are mapped to the given user. + * @param user The user to get the groups for. + * @return The set of groups the user belongs to. + * @throws IOException + */ + public String[] getGroupsForUser(String user) throws IOException; +} diff --git a/src/test/core/org/apache/hadoop/tools/GetGroupsTestBase.java b/src/test/core/org/apache/hadoop/tools/GetGroupsTestBase.java new file mode 100644 index 0000000000..f223f1b18e --- /dev/null +++ b/src/test/core/org/apache/hadoop/tools/GetGroupsTestBase.java @@ -0,0 +1,127 @@ +/** + * 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.tools; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.util.Tool; +import org.apache.hadoop.util.ToolRunner; +import org.junit.Before; +import org.junit.Test; + +public abstract class GetGroupsTestBase { + + protected Configuration conf; + private UserGroupInformation testUser1; + private UserGroupInformation testUser2; + + protected abstract Tool getTool(PrintStream o); + + @Before + public void setUpUsers() throws IOException { + // Make sure the current user's info is in the list of test users. + UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + UserGroupInformation.createUserForTesting(currentUser.getUserName(), currentUser.getGroupNames()); + + testUser1 = UserGroupInformation.createUserForTesting("foo", new String[]{"bar", "baz"}); + testUser2 = UserGroupInformation.createUserForTesting("fiz", new String[]{"buz", "boz"}); + } + + @Test + public void testNoUserGiven() throws Exception { + String actualOutput = runTool(conf, new String[0], true); + UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + assertEquals("No user provided should default to current user", + getExpectedOutput(currentUser), actualOutput); + } + + @Test + public void testExistingUser() throws Exception { + String actualOutput = runTool(conf, new String[]{testUser1.getUserName()}, true); + assertEquals("Show only the output of the user given", + getExpectedOutput(testUser1), actualOutput); + } + + @Test + public void testMultipleExistingUsers() throws Exception { + String actualOutput = runTool(conf, + new String[]{testUser1.getUserName(), testUser2.getUserName()}, true); + assertEquals("Show the output for both users given", + getExpectedOutput(testUser1) + getExpectedOutput(testUser2), actualOutput); + } + + @Test + public void testNonExistentUser() throws Exception { + String actualOutput = runTool(conf, + new String[]{"does-not-exist"}, true); + assertEquals("Show the output for only the user given, with no groups", + getExpectedOutput(UserGroupInformation.createRemoteUser("does-not-exist")), + actualOutput); + } + + @Test + public void testMultipleNonExistingUsers() throws Exception { + String actualOutput = runTool(conf, + new String[]{"does-not-exist1", "does-not-exist2"}, true); + assertEquals("Show the output for only the user given, with no groups", + getExpectedOutput(UserGroupInformation.createRemoteUser("does-not-exist1")) + + getExpectedOutput(UserGroupInformation.createRemoteUser("does-not-exist2")), + actualOutput); + } + + @Test + public void testExistingInterleavedWithNonExistentUsers() throws Exception { + String actualOutput = runTool(conf, + new String[]{"does-not-exist1", testUser1.getUserName(), + "does-not-exist2", testUser2.getUserName()}, true); + assertEquals("Show the output for only the user given, with no groups", + getExpectedOutput(UserGroupInformation.createRemoteUser("does-not-exist1")) + + getExpectedOutput(testUser1) + + getExpectedOutput(UserGroupInformation.createRemoteUser("does-not-exist2")) + + getExpectedOutput(testUser2), + actualOutput); + } + + private static String getExpectedOutput(UserGroupInformation user) { + String expectedOutput = user.getUserName() + " :"; + for (String group : user.getGroupNames()) { + expectedOutput += " " + group; + } + return expectedOutput + "\n"; + } + + private String runTool(Configuration conf, String[] args, boolean success) + throws Exception { + ByteArrayOutputStream o = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(o, true); + try { + int ret = ToolRunner.run(getTool(out), args); + assertEquals(success, ret == 0); + return o.toString(); + } finally { + o.close(); + out.close(); + } + } +}