From 9833468302bd2fa235d9d1f40517631f9dfff517 Mon Sep 17 00:00:00 2001
From: Todd Lipcon
Date: Fri, 20 Jul 2012 18:58:58 +0000
Subject: [PATCH 01/39] HDFS-3597. SNN fails to start after DFS upgrade.
Contributed by Andy Isaacson.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1363899 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 +
.../server/namenode/CheckpointSignature.java | 17 ++-
.../server/namenode/SecondaryNameNode.java | 16 ++-
.../TestSecondaryNameNodeUpgrade.java | 116 ++++++++++++++++++
4 files changed, 137 insertions(+), 14 deletions(-)
create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSecondaryNameNodeUpgrade.java
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 7d108d9de1a..cc16ac35638 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -518,6 +518,8 @@ Branch-2 ( Unreleased changes )
HDFS-3690. BlockPlacementPolicyDefault incorrectly casts LOG. (eli)
+ HDFS-3597. SNN fails to start after DFS upgrade. (Andy Isaacson via todd)
+
BREAKDOWN OF HDFS-3042 SUBTASKS
HDFS-2185. HDFS portion of ZK-based FailoverController (todd)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/CheckpointSignature.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/CheckpointSignature.java
index d723ede9bf1..8c4d79cc9bf 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/CheckpointSignature.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/CheckpointSignature.java
@@ -113,12 +113,19 @@ public class CheckpointSignature extends StorageInfo
+ blockpoolID ;
}
+ boolean storageVersionMatches(StorageInfo si) throws IOException {
+ return (layoutVersion == si.layoutVersion) && (cTime == si.cTime);
+ }
+
+ boolean isSameCluster(FSImage si) {
+ return namespaceID == si.getStorage().namespaceID &&
+ clusterID.equals(si.getClusterID()) &&
+ blockpoolID.equals(si.getBlockPoolID());
+ }
+
void validateStorageInfo(FSImage si) throws IOException {
- if(layoutVersion != si.getStorage().layoutVersion
- || namespaceID != si.getStorage().namespaceID
- || cTime != si.getStorage().cTime
- || !clusterID.equals(si.getClusterID())
- || !blockpoolID.equals(si.getBlockPoolID())) {
+ if (!isSameCluster(si)
+ || !storageVersionMatches(si.getStorage())) {
throw new IOException("Inconsistent checkpoint fields.\n"
+ "LV = " + layoutVersion + " namespaceID = " + namespaceID
+ " cTime = " + cTime
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/SecondaryNameNode.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/SecondaryNameNode.java
index c32bd3383b7..8057955dfac 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/SecondaryNameNode.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/SecondaryNameNode.java
@@ -437,18 +437,16 @@ public class SecondaryNameNode implements Runnable {
// Returns a token that would be used to upload the merged image.
CheckpointSignature sig = namenode.rollEditLog();
- // Make sure we're talking to the same NN!
- if (checkpointImage.getNamespaceID() != 0) {
- // If the image actually has some data, make sure we're talking
- // to the same NN as we did before.
- sig.validateStorageInfo(checkpointImage);
- } else {
- // if we're a fresh 2NN, just take the storage info from the server
- // we first talk to.
+ if ((checkpointImage.getNamespaceID() == 0) ||
+ (sig.isSameCluster(checkpointImage) &&
+ !sig.storageVersionMatches(checkpointImage.getStorage()))) {
+ // if we're a fresh 2NN, or if we're on the same cluster and our storage
+ // needs an upgrade, just take the storage info from the server.
dstStorage.setStorageInfo(sig);
dstStorage.setClusterID(sig.getClusterID());
dstStorage.setBlockPoolID(sig.getBlockpoolID());
}
+ sig.validateStorageInfo(checkpointImage);
// error simulation code for junit test
CheckpointFaultInjector.getInstance().afterSecondaryCallsRollEditLog();
@@ -703,7 +701,7 @@ public class SecondaryNameNode implements Runnable {
/**
* Analyze checkpoint directories.
* Create directories if they do not exist.
- * Recover from an unsuccessful checkpoint is necessary.
+ * Recover from an unsuccessful checkpoint if necessary.
*
* @throws IOException
*/
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSecondaryNameNodeUpgrade.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSecondaryNameNodeUpgrade.java
new file mode 100644
index 00000000000..6119584c0d8
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestSecondaryNameNodeUpgrade.java
@@ -0,0 +1,116 @@
+/**
+ * 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.hdfs.server.namenode;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.After;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.FileUtil;
+
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.hdfs.HdfsConfiguration;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+
+import java.util.Properties;
+import java.io.FileReader;
+import java.io.FileWriter;
+import org.junit.Assert;
+import org.apache.hadoop.test.GenericTestUtils;
+
+/**
+ * Regression test for HDFS-3597, SecondaryNameNode upgrade -- when a 2NN
+ * starts up with an existing directory structure with an old VERSION file, it
+ * should delete the snapshot and download a new one from the NN.
+ */
+public class TestSecondaryNameNodeUpgrade {
+
+ @Before
+ public void cleanupCluster() throws IOException {
+ File hdfsDir = new File(MiniDFSCluster.getBaseDirectory()).getCanonicalFile();
+ System.out.println("cleanupCluster deleting " + hdfsDir);
+ if (hdfsDir.exists() && !FileUtil.fullyDelete(hdfsDir)) {
+ throw new IOException("Could not delete hdfs directory '" + hdfsDir + "'");
+ }
+ }
+
+ private void doIt(String param, String val) throws IOException {
+ MiniDFSCluster cluster = null;
+ FileSystem fs = null;
+ SecondaryNameNode snn = null;
+
+ try {
+ Configuration conf = new HdfsConfiguration();
+
+ cluster = new MiniDFSCluster.Builder(conf).build();
+ cluster.waitActive();
+
+ conf.set(DFSConfigKeys.DFS_NAMENODE_SECONDARY_HTTP_ADDRESS_KEY, "0.0.0.0:0");
+ snn = new SecondaryNameNode(conf);
+
+ fs = cluster.getFileSystem();
+
+ fs.mkdirs(new Path("/test/foo"));
+
+ snn.doCheckpoint();
+
+ List versionFiles = snn.getFSImage().getStorage().getFiles(null, "VERSION");
+
+ snn.shutdown();
+
+ for (File versionFile : versionFiles) {
+ System.out.println("Changing '" + param + "' to '" + val + "' in " + versionFile);
+ FSImageTestUtil.corruptVersionFile(versionFile, param, val);
+ }
+
+ snn = new SecondaryNameNode(conf);
+
+ fs.mkdirs(new Path("/test/bar"));
+
+ snn.doCheckpoint();
+ } finally {
+ if (fs != null) fs.close();
+ if (cluster != null) cluster.shutdown();
+ if (snn != null) snn.shutdown();
+ }
+ }
+
+ @Test
+ public void testUpgradeLayoutVersionSucceeds() throws IOException {
+ doIt("layoutVersion", "-39");
+ }
+
+ @Test
+ public void testChangeNsIDFails() throws IOException {
+ try {
+ doIt("namespaceID", "2");
+ Assert.fail("Should throw InconsistentFSStateException");
+ } catch(IOException e) {
+ GenericTestUtils.assertExceptionContains("Inconsistent checkpoint fields", e);
+ System.out.println("Correctly failed with inconsistent namespaceID: " + e);
+ }
+ }
+}
From 02a5551ba3d3d44b79bf594816263d625cdca36f Mon Sep 17 00:00:00 2001
From: Aaron Myers
Date: Fri, 20 Jul 2012 19:15:52 +0000
Subject: [PATCH 02/39] HDFS-3608. fuse_dfs: detect changes in UID ticket
cache. Contributed by Colin Patrick McCabe.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1363904 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 3 +
.../hadoop-hdfs/src/CMakeLists.txt | 1 +
.../src/main/native/fuse-dfs/CMakeLists.txt | 1 +
.../src/main/native/fuse-dfs/fuse_connect.c | 638 ++++++++++++---
.../src/main/native/fuse-dfs/fuse_connect.h | 70 +-
.../native/fuse-dfs/fuse_context_handle.h | 2 -
.../src/main/native/fuse-dfs/fuse_dfs.c | 37 +-
.../main/native/fuse-dfs/fuse_file_handle.h | 4 +-
.../main/native/fuse-dfs/fuse_impls_chmod.c | 16 +-
.../main/native/fuse-dfs/fuse_impls_chown.c | 28 +-
.../main/native/fuse-dfs/fuse_impls_flush.c | 5 +-
.../main/native/fuse-dfs/fuse_impls_getattr.c | 28 +-
.../main/native/fuse-dfs/fuse_impls_mkdir.c | 28 +-
.../main/native/fuse-dfs/fuse_impls_open.c | 65 +-
.../main/native/fuse-dfs/fuse_impls_read.c | 9 +-
.../main/native/fuse-dfs/fuse_impls_readdir.c | 26 +-
.../main/native/fuse-dfs/fuse_impls_release.c | 6 +-
.../main/native/fuse-dfs/fuse_impls_rename.c | 29 +-
.../main/native/fuse-dfs/fuse_impls_rmdir.c | 46 +-
.../main/native/fuse-dfs/fuse_impls_statfs.c | 35 +-
.../native/fuse-dfs/fuse_impls_truncate.c | 22 +-
.../main/native/fuse-dfs/fuse_impls_unlink.c | 31 +-
.../main/native/fuse-dfs/fuse_impls_utimens.c | 25 +-
.../main/native/fuse-dfs/fuse_impls_write.c | 7 +-
.../src/main/native/fuse-dfs/fuse_init.c | 26 +-
.../src/main/native/libhdfs/hdfs.c | 72 +-
.../src/main/native/libhdfs/hdfs.h | 24 +-
.../src/main/native/libhdfs/jni_helper.c | 6 +
.../hadoop-hdfs/src/main/native/util/tree.h | 765 ++++++++++++++++++
.../src/main/resources/hdfs-default.xml | 21 +
30 files changed, 1740 insertions(+), 336 deletions(-)
create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/main/native/util/tree.h
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index cc16ac35638..7a2c6acf994 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -520,6 +520,9 @@ Branch-2 ( Unreleased changes )
HDFS-3597. SNN fails to start after DFS upgrade. (Andy Isaacson via todd)
+ HDFS-3608. fuse_dfs: detect changes in UID ticket cache. (Colin Patrick
+ McCabe via atm)
+
BREAKDOWN OF HDFS-3042 SUBTASKS
HDFS-2185. HDFS portion of ZK-based FailoverController (todd)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/CMakeLists.txt b/hadoop-hdfs-project/hadoop-hdfs/src/CMakeLists.txt
index 4a96bbd7a0f..7c1441fca52 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/CMakeLists.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/CMakeLists.txt
@@ -87,6 +87,7 @@ include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_BINARY_DIR}
${JNI_INCLUDE_DIRS}
+ main/native
main/native/libhdfs
)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/CMakeLists.txt b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/CMakeLists.txt
index fb3c580e94c..f04870957e7 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/CMakeLists.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/CMakeLists.txt
@@ -69,5 +69,6 @@ IF(FUSE_FOUND)
${JAVA_JVM_LIBRARY}
hdfs
m
+ pthread
)
ENDIF(FUSE_FOUND)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c
index bfb7a1eedb7..c6624fad999 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c
@@ -16,17 +16,38 @@
* limitations under the License.
*/
-#include "hdfs.h"
-#include "fuse_dfs.h"
#include "fuse_connect.h"
+#include "fuse_dfs.h"
#include "fuse_users.h"
+#include "libhdfs/hdfs.h"
+#include "util/tree.h"
+#include
#include
+#include
#include
#include
#include
+#include
+#include
+#include
-#define HADOOP_SECURITY_AUTHENTICATION "hadoop.security.authentication"
+#define FUSE_CONN_DEFAULT_TIMER_PERIOD 5
+#define FUSE_CONN_DEFAULT_EXPIRY_PERIOD (5 * 60)
+#define HADOOP_SECURITY_AUTHENTICATION "hadoop.security.authentication"
+#define HADOOP_FUSE_CONNECTION_TIMEOUT "hadoop.fuse.connection.timeout"
+#define HADOOP_FUSE_TIMER_PERIOD "hadoop.fuse.timer.period"
+
+/** Length of the buffer needed by asctime_r */
+#define TIME_STR_LEN 26
+
+struct hdfsConn;
+
+static int hdfsConnCompare(const struct hdfsConn *a, const struct hdfsConn *b);
+static void hdfsConnExpiry(void);
+static void* hdfsConnExpiryThread(void *v);
+
+RB_HEAD(hdfsConnTree, hdfsConn);
enum authConf {
AUTH_CONF_UNKNOWN,
@@ -34,58 +55,54 @@ enum authConf {
AUTH_CONF_OTHER,
};
-#define MAX_ELEMENTS (16 * 1024)
-static struct hsearch_data *fsTable = NULL;
-static enum authConf hdfsAuthConf = AUTH_CONF_UNKNOWN;
-static pthread_mutex_t tableMutex = PTHREAD_MUTEX_INITIALIZER;
+struct hdfsConn {
+ RB_ENTRY(hdfsConn) entry;
+ /** How many threads are currently using this hdfsConnection object */
+ int64_t refcnt;
+ /** The username used to make this connection. Dynamically allocated. */
+ char *usrname;
+ /** Kerberos ticket cache path, or NULL if this is not a kerberized
+ * connection. Dynamically allocated. */
+ char *kpath;
+ /** mtime of the kpath, if the kpath is non-NULL */
+ time_t kPathMtime;
+ /** nanosecond component of the mtime of the kpath, if the kpath is non-NULL */
+ long kPathMtimeNs;
+ /** The cached libhdfs fs instance */
+ hdfsFS fs;
+ /** Nonzero if this hdfs connection needs to be closed as soon as possible.
+ * If this is true, the connection has been removed from the tree. */
+ int condemned;
+ /** Number of times we should run the expiration timer on this connection
+ * before removing it. */
+ int expirationCount;
+};
-/*
- * Allocate a hash table for fs handles. Returns 0 on success,
- * -1 on failure.
- */
-int allocFsTable(void) {
- assert(NULL == fsTable);
- fsTable = calloc(1, sizeof(struct hsearch_data));
- if (0 == hcreate_r(MAX_ELEMENTS, fsTable)) {
- ERROR("Unable to initialize connection table");
- return -1;
- }
- return 0;
-}
+RB_GENERATE(hdfsConnTree, hdfsConn, entry, hdfsConnCompare);
-/*
- * Find a fs handle for the given key. Returns a fs handle,
- * or NULL if there is no fs for the given key.
- */
-static hdfsFS findFs(char *key) {
- ENTRY entry;
- ENTRY *entryP = NULL;
- entry.key = key;
- if (0 == hsearch_r(entry, FIND, &entryP, fsTable)) {
- return NULL;
- }
- assert(NULL != entryP->data);
- return (hdfsFS)entryP->data;
-}
+/** Current cached libhdfs connections */
+static struct hdfsConnTree gConnTree;
-/*
- * Insert the given fs handle into the table.
- * Returns 0 on success, -1 on failure.
- */
-static int insertFs(char *key, hdfsFS fs) {
- ENTRY entry;
- ENTRY *entryP = NULL;
- assert(NULL != fs);
- entry.key = strdup(key);
- if (entry.key == NULL) {
- return -1;
- }
- entry.data = (void*)fs;
- if (0 == hsearch_r(entry, ENTER, &entryP, fsTable)) {
- return -1;
- }
- return 0;
-}
+/** The URI used to make our connections. Dynamically allocated. */
+static char *gUri;
+
+/** The port used to make our connections, or 0. */
+static int gPort;
+
+/** Lock which protects gConnTree and gConnectTimer->active */
+static pthread_mutex_t gConnMutex;
+
+/** Type of authentication configured */
+static enum authConf gHdfsAuthConf;
+
+/** FUSE connection timer expiration period */
+static int32_t gTimerPeriod;
+
+/** FUSE connection expiry period */
+static int32_t gExpiryPeriod;
+
+/** FUSE timer expiration thread */
+static pthread_t gTimerThread;
/**
* Find out what type of authentication the system administrator
@@ -95,19 +112,251 @@ static int insertFs(char *key, hdfsFS fs) {
*/
static enum authConf discoverAuthConf(void)
{
- int ret;
- char *val = NULL;
- enum authConf authConf;
+ int ret;
+ char *val = NULL;
+ enum authConf authConf;
- ret = hdfsConfGet(HADOOP_SECURITY_AUTHENTICATION, &val);
- if (ret)
- authConf = AUTH_CONF_UNKNOWN;
- else if (!strcmp(val, "kerberos"))
- authConf = AUTH_CONF_KERBEROS;
- else
- authConf = AUTH_CONF_OTHER;
- free(val);
- return authConf;
+ ret = hdfsConfGetStr(HADOOP_SECURITY_AUTHENTICATION, &val);
+ if (ret)
+ authConf = AUTH_CONF_UNKNOWN;
+ else if (!val)
+ authConf = AUTH_CONF_OTHER;
+ else if (!strcmp(val, "kerberos"))
+ authConf = AUTH_CONF_KERBEROS;
+ else
+ authConf = AUTH_CONF_OTHER;
+ free(val);
+ return authConf;
+}
+
+int fuseConnectInit(const char *nnUri, int port)
+{
+ const char *timerPeriod;
+ int ret;
+
+ gTimerPeriod = FUSE_CONN_DEFAULT_TIMER_PERIOD;
+ ret = hdfsConfGetInt(HADOOP_FUSE_CONNECTION_TIMEOUT, &gTimerPeriod);
+ if (ret) {
+ fprintf(stderr, "Unable to determine the configured value for %s.",
+ HADOOP_FUSE_TIMER_PERIOD);
+ return -EINVAL;
+ }
+ if (gTimerPeriod < 1) {
+ fprintf(stderr, "Invalid value %d given for %s.\n",
+ gTimerPeriod, HADOOP_FUSE_TIMER_PERIOD);
+ return -EINVAL;
+ }
+ gExpiryPeriod = FUSE_CONN_DEFAULT_EXPIRY_PERIOD;
+ ret = hdfsConfGetInt(HADOOP_FUSE_CONNECTION_TIMEOUT, &gExpiryPeriod);
+ if (ret) {
+ fprintf(stderr, "Unable to determine the configured value for %s.",
+ HADOOP_FUSE_CONNECTION_TIMEOUT);
+ return -EINVAL;
+ }
+ if (gExpiryPeriod < 1) {
+ fprintf(stderr, "Invalid value %d given for %s.\n",
+ gExpiryPeriod, HADOOP_FUSE_CONNECTION_TIMEOUT);
+ return -EINVAL;
+ }
+ gHdfsAuthConf = discoverAuthConf();
+ if (gHdfsAuthConf == AUTH_CONF_UNKNOWN) {
+ fprintf(stderr, "Unable to determine the configured value for %s.",
+ HADOOP_SECURITY_AUTHENTICATION);
+ return -EINVAL;
+ }
+ gPort = port;
+ gUri = strdup(nnUri);
+ if (!gUri) {
+ fprintf(stderr, "fuseConnectInit: OOM allocting nnUri\n");
+ return -ENOMEM;
+ }
+ ret = pthread_mutex_init(&gConnMutex, NULL);
+ if (ret) {
+ free(gUri);
+ fprintf(stderr, "fuseConnectInit: pthread_mutex_init failed with error %d\n",
+ ret);
+ return -ret;
+ }
+ RB_INIT(&gConnTree);
+ ret = pthread_create(&gTimerThread, NULL, hdfsConnExpiryThread, NULL);
+ if (ret) {
+ free(gUri);
+ pthread_mutex_destroy(&gConnMutex);
+ fprintf(stderr, "fuseConnectInit: pthread_create failed with error %d\n",
+ ret);
+ return -ret;
+ }
+ fprintf(stderr, "fuseConnectInit: initialized with timer period %d, "
+ "expiry period %d\n", gTimerPeriod, gExpiryPeriod);
+ return 0;
+}
+
+/**
+ * Compare two libhdfs connections by username
+ *
+ * @param a The first libhdfs connection
+ * @param b The second libhdfs connection
+ *
+ * @return -1, 0, or 1 depending on a < b, a ==b, a > b
+ */
+static int hdfsConnCompare(const struct hdfsConn *a, const struct hdfsConn *b)
+{
+ return strcmp(a->usrname, b->usrname);
+}
+
+/**
+ * Find a libhdfs connection by username
+ *
+ * @param usrname The username to look up
+ *
+ * @return The connection, or NULL if none could be found
+ */
+static struct hdfsConn* hdfsConnFind(const char *usrname)
+{
+ struct hdfsConn exemplar;
+
+ memset(&exemplar, 0, sizeof(exemplar));
+ exemplar.usrname = (char*)usrname;
+ return RB_FIND(hdfsConnTree, &gConnTree, &exemplar);
+}
+
+/**
+ * Free the resource associated with a libhdfs connection.
+ *
+ * You must remove the connection from the tree before calling this function.
+ *
+ * @param conn The libhdfs connection
+ */
+static void hdfsConnFree(struct hdfsConn *conn)
+{
+ int ret;
+
+ ret = hdfsDisconnect(conn->fs);
+ if (ret) {
+ fprintf(stderr, "hdfsConnFree(username=%s): "
+ "hdfsDisconnect failed with error %d\n",
+ (conn->usrname ? conn->usrname : "(null)"), ret);
+ }
+ free(conn->usrname);
+ free(conn->kpath);
+ free(conn);
+}
+
+/**
+ * Convert a time_t to a string.
+ *
+ * @param sec time in seconds since the epoch
+ * @param buf (out param) output buffer
+ * @param bufLen length of output buffer
+ *
+ * @return 0 on success; ENAMETOOLONG if the provided buffer was
+ * too short
+ */
+static int timeToStr(time_t sec, char *buf, size_t bufLen)
+{
+ struct tm tm, *out;
+ size_t l;
+
+ if (bufLen < TIME_STR_LEN) {
+ return -ENAMETOOLONG;
+ }
+ out = localtime_r(&sec, &tm);
+ asctime_r(out, buf);
+ // strip trailing newline
+ l = strlen(buf);
+ if (l != 0)
+ buf[l - 1] = '\0';
+ return 0;
+}
+
+/**
+ * Check an HDFS connection's Kerberos path.
+ *
+ * If the mtime of the Kerberos ticket cache file has changed since we first
+ * opened the connection, mark the connection as condemned and remove it from
+ * the hdfs connection tree.
+ *
+ * @param conn The HDFS connection
+ */
+static int hdfsConnCheckKpath(const struct hdfsConn *conn)
+{
+ int ret;
+ struct stat st;
+ char prevTimeBuf[TIME_STR_LEN], newTimeBuf[TIME_STR_LEN];
+
+ if (stat(conn->kpath, &st) < 0) {
+ ret = errno;
+ if (ret == ENOENT) {
+ fprintf(stderr, "hdfsConnCheckKpath(conn.usrname=%s): the kerberos "
+ "ticket cache file '%s' has disappeared. Condemning the "
+ "connection.\n", conn->usrname, conn->kpath);
+ } else {
+ fprintf(stderr, "hdfsConnCheckKpath(conn.usrname=%s): stat(%s) "
+ "failed with error code %d. Pessimistically condemning the "
+ "connection.\n", conn->usrname, conn->kpath, ret);
+ }
+ return -ret;
+ }
+ if ((st.st_mtim.tv_sec != conn->kPathMtime) ||
+ (st.st_mtim.tv_nsec != conn->kPathMtimeNs)) {
+ timeToStr(conn->kPathMtime, prevTimeBuf, sizeof(prevTimeBuf));
+ timeToStr(st.st_mtim.tv_sec, newTimeBuf, sizeof(newTimeBuf));
+ fprintf(stderr, "hdfsConnCheckKpath(conn.usrname=%s): mtime on '%s' "
+ "has changed from '%s' to '%s'. Condemning the connection "
+ "because our cached Kerberos credentials have probably "
+ "changed.\n", conn->usrname, conn->kpath, prevTimeBuf, newTimeBuf);
+ return -EINTERNAL;
+ }
+ return 0;
+}
+
+/**
+ * Cache expiration logic.
+ *
+ * This function is called periodically by the cache expiration thread. For
+ * each FUSE connection not currently in use (refcnt == 0) it will decrement the
+ * expirationCount for that connection. Once the expirationCount reaches 0 for
+ * a connection, it can be garbage collected.
+ *
+ * We also check to see if the Kerberos credentials have changed. If so, the
+ * connecton is immediately condemned, even if it is currently in use.
+ */
+static void hdfsConnExpiry(void)
+{
+ struct hdfsConn *conn, *tmpConn;
+
+ pthread_mutex_lock(&gConnMutex);
+ RB_FOREACH_SAFE(conn, hdfsConnTree, &gConnTree, tmpConn) {
+ if (conn->kpath) {
+ if (hdfsConnCheckKpath(conn)) {
+ conn->condemned = 1;
+ RB_REMOVE(hdfsConnTree, &gConnTree, conn);
+ if (conn->refcnt == 0) {
+ /* If the connection is not in use by any threads, delete it
+ * immediately. If it is still in use by some threads, the last
+ * thread using it will clean it up later inside hdfsConnRelease. */
+ hdfsConnFree(conn);
+ continue;
+ }
+ }
+ }
+ if (conn->refcnt == 0) {
+ /* If the connection is not currently in use by a thread, check to see if
+ * it ought to be removed because it's too old. */
+ conn->expirationCount--;
+ if (conn->expirationCount <= 0) {
+ if (conn->condemned) {
+ fprintf(stderr, "hdfsConnExpiry: LOGIC ERROR: condemned connection "
+ "as %s is still in the tree!\n", conn->usrname);
+ }
+ fprintf(stderr, "hdfsConnExpiry: freeing and removing connection as "
+ "%s because it's now too old.\n", conn->usrname);
+ RB_REMOVE(hdfsConnTree, &gConnTree, conn);
+ hdfsConnFree(conn);
+ }
+ }
+ }
+ pthread_mutex_unlock(&gConnMutex);
}
/**
@@ -129,9 +378,9 @@ static enum authConf discoverAuthConf(void)
* @param path (out param) the path to the ticket cache file
* @param pathLen length of the path buffer
*/
-static void findKerbTicketCachePath(char *path, size_t pathLen)
+static void findKerbTicketCachePath(struct fuse_context *ctx,
+ char *path, size_t pathLen)
{
- struct fuse_context *ctx = fuse_get_context();
FILE *fp = NULL;
static const char * const KRB5CCNAME = "\0KRB5CCNAME=";
int c = '\0', pathIdx = 0, keyIdx = 0;
@@ -168,72 +417,213 @@ done:
}
}
-/*
- * Connect to the NN as the current user/group.
- * Returns a fs handle on success, or NULL on failure.
+/**
+ * Create a new libhdfs connection.
+ *
+ * @param usrname Username to use for the new connection
+ * @param ctx FUSE context to use for the new connection
+ * @param out (out param) the new libhdfs connection
+ *
+ * @return 0 on success; error code otherwise
*/
-hdfsFS doConnectAsUser(const char *nn_uri, int nn_port) {
- struct hdfsBuilder *bld;
- uid_t uid = fuse_get_context()->uid;
- char *user = getUsername(uid);
- char kpath[PATH_MAX];
+static int fuseNewConnect(const char *usrname, struct fuse_context *ctx,
+ struct hdfsConn **out)
+{
+ struct hdfsBuilder *bld = NULL;
+ char kpath[PATH_MAX] = { 0 };
+ struct hdfsConn *conn = NULL;
int ret;
- hdfsFS fs = NULL;
- if (NULL == user) {
- goto done;
+ struct stat st;
+
+ conn = calloc(1, sizeof(struct hdfsConn));
+ if (!conn) {
+ fprintf(stderr, "fuseNewConnect: OOM allocating struct hdfsConn\n");
+ ret = -ENOMEM;
+ goto error;
}
-
- ret = pthread_mutex_lock(&tableMutex);
- assert(0 == ret);
-
- fs = findFs(user);
- if (NULL == fs) {
- if (hdfsAuthConf == AUTH_CONF_UNKNOWN) {
- hdfsAuthConf = discoverAuthConf();
- if (hdfsAuthConf == AUTH_CONF_UNKNOWN) {
- ERROR("Unable to determine the configured value for %s.",
- HADOOP_SECURITY_AUTHENTICATION);
- goto done;
- }
+ bld = hdfsNewBuilder();
+ if (!bld) {
+ fprintf(stderr, "Unable to create hdfs builder\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+ /* We always want to get a new FileSystem instance here-- that's why we call
+ * hdfsBuilderSetForceNewInstance. Otherwise the 'cache condemnation' logic
+ * in hdfsConnExpiry will not work correctly, since FileSystem might re-use the
+ * existing cached connection which we wanted to get rid of.
+ */
+ hdfsBuilderSetForceNewInstance(bld);
+ hdfsBuilderSetNameNode(bld, gUri);
+ if (gPort) {
+ hdfsBuilderSetNameNodePort(bld, gPort);
+ }
+ hdfsBuilderSetUserName(bld, usrname);
+ if (gHdfsAuthConf == AUTH_CONF_KERBEROS) {
+ findKerbTicketCachePath(ctx, kpath, sizeof(kpath));
+ if (stat(kpath, &st) < 0) {
+ fprintf(stderr, "fuseNewConnect: failed to find Kerberos ticket cache "
+ "file '%s'. Did you remember to kinit for UID %d?\n",
+ kpath, ctx->uid);
+ ret = -EACCES;
+ goto error;
}
- bld = hdfsNewBuilder();
- if (!bld) {
- ERROR("Unable to create hdfs builder");
- goto done;
- }
- hdfsBuilderSetForceNewInstance(bld);
- hdfsBuilderSetNameNode(bld, nn_uri);
- if (nn_port) {
- hdfsBuilderSetNameNodePort(bld, nn_port);
- }
- hdfsBuilderSetUserName(bld, user);
- if (hdfsAuthConf == AUTH_CONF_KERBEROS) {
- findKerbTicketCachePath(kpath, sizeof(kpath));
- hdfsBuilderSetKerbTicketCachePath(bld, kpath);
- }
- fs = hdfsBuilderConnect(bld);
- if (NULL == fs) {
- int err = errno;
- ERROR("Unable to create fs for user %s: error code %d", user, err);
- goto done;
- }
- if (-1 == insertFs(user, fs)) {
- ERROR("Unable to cache fs for user %s", user);
+ conn->kPathMtime = st.st_mtim.tv_sec;
+ conn->kPathMtimeNs = st.st_mtim.tv_nsec;
+ hdfsBuilderSetKerbTicketCachePath(bld, kpath);
+ conn->kpath = strdup(kpath);
+ if (!conn->kpath) {
+ fprintf(stderr, "fuseNewConnect: OOM allocating kpath\n");
+ ret = -ENOMEM;
+ goto error;
}
}
+ conn->usrname = strdup(usrname);
+ if (!conn->usrname) {
+ fprintf(stderr, "fuseNewConnect: OOM allocating usrname\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+ conn->fs = hdfsBuilderConnect(bld);
+ bld = NULL;
+ if (!conn->fs) {
+ ret = errno;
+ fprintf(stderr, "fuseNewConnect(usrname=%s): Unable to create fs: "
+ "error code %d\n", usrname, ret);
+ goto error;
+ }
+ RB_INSERT(hdfsConnTree, &gConnTree, conn);
+ *out = conn;
+ return 0;
-done:
- ret = pthread_mutex_unlock(&tableMutex);
- assert(0 == ret);
- free(user);
- return fs;
+error:
+ if (bld) {
+ hdfsFreeBuilder(bld);
+ }
+ if (conn) {
+ free(conn->kpath);
+ free(conn->usrname);
+ free(conn);
+ }
+ return ret;
}
-/*
- * We currently cache a fs handle per-user in this module rather
- * than use the FileSystem cache in the java client. Therefore
- * we do not disconnect the fs handle here.
- */
-int doDisconnect(hdfsFS fs) {
+int fuseConnect(const char *usrname, struct fuse_context *ctx,
+ struct hdfsConn **out)
+{
+ int ret;
+ struct hdfsConn* conn;
+
+ pthread_mutex_lock(&gConnMutex);
+ conn = hdfsConnFind(usrname);
+ if (!conn) {
+ ret = fuseNewConnect(usrname, ctx, &conn);
+ if (ret) {
+ pthread_mutex_unlock(&gConnMutex);
+ fprintf(stderr, "fuseConnect(usrname=%s): fuseNewConnect failed with "
+ "error code %d\n", usrname, ret);
+ return ret;
+ }
+ }
+ conn->refcnt++;
+ conn->expirationCount = (gExpiryPeriod + gTimerPeriod - 1) / gTimerPeriod;
+ if (conn->expirationCount < 2)
+ conn->expirationCount = 2;
+ pthread_mutex_unlock(&gConnMutex);
+ *out = conn;
return 0;
}
+
+int fuseConnectAsThreadUid(struct hdfsConn **conn)
+{
+ struct fuse_context *ctx;
+ char *usrname;
+ int ret;
+
+ ctx = fuse_get_context();
+ usrname = getUsername(ctx->uid);
+ ret = fuseConnect(usrname, ctx, conn);
+ free(usrname);
+ return ret;
+}
+
+int fuseConnectTest(void)
+{
+ int ret;
+ struct hdfsConn *conn;
+
+ if (gHdfsAuthConf == AUTH_CONF_KERBEROS) {
+ // TODO: call some method which can tell us whether the FS exists. In order
+ // to implement this, we have to add a method to FileSystem in order to do
+ // this without valid Kerberos authentication. See HDFS-3674 for details.
+ return 0;
+ }
+ ret = fuseNewConnect("root", NULL, &conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectTest failed with error code %d\n", ret);
+ return ret;
+ }
+ hdfsConnRelease(conn);
+ return 0;
+}
+
+struct hdfs_internal* hdfsConnGetFs(struct hdfsConn *conn)
+{
+ return conn->fs;
+}
+
+void hdfsConnRelease(struct hdfsConn *conn)
+{
+ pthread_mutex_lock(&gConnMutex);
+ conn->refcnt--;
+ if ((conn->refcnt == 0) && (conn->condemned)) {
+ fprintf(stderr, "hdfsConnRelease(usrname=%s): freeing condemend FS!\n",
+ conn->usrname);
+ /* Notice that we're not removing the connection from gConnTree here.
+ * If the connection is condemned, it must have already been removed from
+ * the tree, so that no other threads start using it.
+ */
+ hdfsConnFree(conn);
+ }
+ pthread_mutex_unlock(&gConnMutex);
+}
+
+/**
+ * Get the monotonic time.
+ *
+ * Unlike the wall-clock time, monotonic time only ever goes forward. If the
+ * user adjusts the time, the monotonic time will not be affected.
+ *
+ * @return The monotonic time
+ */
+static time_t getMonotonicTime(void)
+{
+ int res;
+ struct timespec ts;
+
+ res = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (res)
+ abort();
+ return ts.tv_sec;
+}
+
+/**
+ * FUSE connection expiration thread
+ *
+ */
+static void* hdfsConnExpiryThread(void *v)
+{
+ time_t nextTime, curTime;
+ int waitTime;
+
+ nextTime = getMonotonicTime() + gTimerPeriod;
+ while (1) {
+ curTime = getMonotonicTime();
+ if (curTime >= nextTime) {
+ hdfsConnExpiry();
+ nextTime = curTime + gTimerPeriod;
+ }
+ waitTime = (nextTime - curTime) * 1000;
+ poll(NULL, 0, waitTime);
+ }
+ return NULL;
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.h b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.h
index 4bddeeae9f0..35645c66b6f 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.h
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.h
@@ -19,10 +19,72 @@
#ifndef __FUSE_CONNECT_H__
#define __FUSE_CONNECT_H__
-#include "fuse_dfs.h"
+struct fuse_context;
+struct hdfsConn;
+struct hdfs_internal;
-hdfsFS doConnectAsUser(const char *nn_uri, int nn_port);
-int doDisconnect(hdfsFS fs);
-int allocFsTable(void);
+/**
+ * Initialize the fuse connection subsystem.
+ *
+ * This must be called before any of the other functions in this module.
+ *
+ * @param nnUri The NameNode URI
+ * @param port The NameNode port
+ *
+ * @return 0 on success; error code otherwise
+ */
+int fuseConnectInit(const char *nnUri, int port);
+
+/**
+ * Get a libhdfs connection.
+ *
+ * If there is an existing connection, it will be reused. If not, a new one
+ * will be created.
+ *
+ * You must call hdfsConnRelease on the connection you get back!
+ *
+ * @param usrname The username to use
+ * @param ctx The FUSE context to use (contains UID, PID of requestor)
+ * @param conn (out param) The HDFS connection
+ *
+ * @return 0 on success; error code otherwise
+ */
+int fuseConnect(const char *usrname, struct fuse_context *ctx,
+ struct hdfsConn **out);
+
+/**
+ * Get a libhdfs connection.
+ *
+ * The same as fuseConnect, except the username will be determined from the FUSE
+ * thread context.
+ *
+ * @param conn (out param) The HDFS connection
+ *
+ * @return 0 on success; error code otherwise
+ */
+int fuseConnectAsThreadUid(struct hdfsConn **conn);
+
+/**
+ * Test whether we can connect to the HDFS cluster
+ *
+ * @return 0 on success; error code otherwise
+ */
+int fuseConnectTest(void);
+
+/**
+ * Get the hdfsFS associated with an hdfsConn.
+ *
+ * @param conn The hdfsConn
+ *
+ * @return the hdfsFS
+ */
+struct hdfs_internal* hdfsConnGetFs(struct hdfsConn *conn);
+
+/**
+ * Release an hdfsConn when we're done with it.
+ *
+ * @param conn The hdfsConn
+ */
+void hdfsConnRelease(struct hdfsConn *conn);
#endif
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_context_handle.h b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_context_handle.h
index ae07735d88e..f2b48be29c5 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_context_handle.h
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_context_handle.h
@@ -31,8 +31,6 @@
//
typedef struct dfs_context_struct {
int debug;
- char *nn_uri;
- int nn_port;
int read_only;
int usetrash;
int direct_io;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_dfs.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_dfs.c
index e218c81c9ec..1a8ede6348e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_dfs.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_dfs.c
@@ -65,8 +65,19 @@ static struct fuse_operations dfs_oper = {
.truncate = dfs_truncate,
};
+static void print_env_vars(void)
+{
+ const char *cp = getenv("CLASSPATH");
+ const char *ld = getenv("LD_LIBRARY_PATH");
+
+ fprintf(stderr, "LD_LIBRARY_PATH=%s",ld == NULL ? "NULL" : ld);
+ fprintf(stderr, "CLASSPATH=%s",cp == NULL ? "NULL" : cp);
+}
+
int main(int argc, char *argv[])
{
+ int ret;
+
umask(0);
extern const char *program;
@@ -106,24 +117,22 @@ int main(int argc, char *argv[])
exit(0);
}
- // Check connection as root
+ ret = fuseConnectInit(options.nn_uri, options.nn_port);
+ if (ret) {
+ ERROR("FATAL: dfs_init: fuseConnInit failed with error %d!", ret);
+ print_env_vars();
+ exit(EXIT_FAILURE);
+ }
if (options.initchecks == 1) {
- hdfsFS tempFS = hdfsConnectAsUser(options.nn_uri, options.nn_port, "root");
- if (NULL == tempFS) {
- const char *cp = getenv("CLASSPATH");
- const char *ld = getenv("LD_LIBRARY_PATH");
- ERROR("FATAL: misconfiguration - cannot connect to HDFS");
- ERROR("LD_LIBRARY_PATH=%s",ld == NULL ? "NULL" : ld);
- ERROR("CLASSPATH=%s",cp == NULL ? "NULL" : cp);
- exit(1);
- }
- if (doDisconnect(tempFS)) {
- ERROR("FATAL: unable to disconnect from test filesystem.");
- exit(1);
+ ret = fuseConnectTest();
+ if (ret) {
+ ERROR("FATAL: dfs_init: fuseConnTest failed with error %d!", ret);
+ print_env_vars();
+ exit(EXIT_FAILURE);
}
}
- int ret = fuse_main(args.argc, args.argv, &dfs_oper, NULL);
+ ret = fuse_main(args.argc, args.argv, &dfs_oper, NULL);
fuse_opt_free_args(&args);
return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_file_handle.h b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_file_handle.h
index 70cd8983a81..7f9346c1e9c 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_file_handle.h
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_file_handle.h
@@ -22,6 +22,8 @@
#include
#include
+struct hdfsConn;
+
/**
*
* dfs_fh_struct is passed around for open files. Fuse provides a hook (the context)
@@ -34,10 +36,10 @@
*/
typedef struct dfs_fh_struct {
hdfsFile hdfsFH;
+ struct hdfsConn *conn;
char *buf;
tSize bufferSize; //what is the size of the buffer we have
off_t buffersStartOffset; //where the buffer starts in the file
- hdfsFS fs; // for reads/writes need to access as the real user
pthread_mutex_t mutex;
} dfs_fh;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_chmod.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_chmod.c
index 2c1e96b2c1b..8c25f53b6a2 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_chmod.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_chmod.c
@@ -23,6 +23,8 @@
int dfs_chmod(const char *path, mode_t mode)
{
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
TRACE1("chmod", path)
int ret = 0;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
@@ -31,22 +33,24 @@ int dfs_chmod(const char *path, mode_t mode)
assert(dfs);
assert('/' == *path);
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect to HDFS");
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
ret = -EIO;
goto cleanup;
}
+ fs = hdfsConnGetFs(conn);
- if (hdfsChmod(userFS, path, (short)mode)) {
+ if (hdfsChmod(fs, path, (short)mode)) {
ERROR("Could not chmod %s to %d", path, (int)mode);
ret = (errno > 0) ? -errno : -EIO;
goto cleanup;
}
cleanup:
- if (doDisconnect(userFS)) {
- ret = -EIO;
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_chown.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_chown.c
index a14ca7137e6..2a6b61c027e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_chown.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_chown.c
@@ -25,12 +25,12 @@
int dfs_chown(const char *path, uid_t uid, gid_t gid)
{
- TRACE1("chown", path)
-
+ struct hdfsConn *conn = NULL;
int ret = 0;
char *user = NULL;
char *group = NULL;
- hdfsFS userFS = NULL;
+
+ TRACE1("chown", path)
// retrieve dfs specific data
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
@@ -61,14 +61,15 @@ int dfs_chown(const char *path, uid_t uid, gid_t gid)
}
}
- userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect to HDFS");
+ ret = fuseConnect(user, fuse_get_context(), &conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnect: failed to open a libhdfs connection! "
+ "error %d.\n", ret);
ret = -EIO;
goto cleanup;
}
- if (hdfsChown(userFS, path, user, group)) {
+ if (hdfsChown(hdfsConnGetFs(conn), path, user, group)) {
ret = errno;
ERROR("Could not chown %s to %d:%d: error %d", path, (int)uid, gid, ret);
ret = (ret > 0) ? -ret : -EIO;
@@ -76,16 +77,11 @@ int dfs_chown(const char *path, uid_t uid, gid_t gid)
}
cleanup:
- if (userFS && doDisconnect(userFS)) {
- ret = -EIO;
- }
- if (user) {
- free(user);
- }
- if (group) {
- free(group);
+ if (conn) {
+ hdfsConnRelease(conn);
}
+ free(user);
+ free(group);
return ret;
-
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_flush.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_flush.c
index 6d4f05c4ec6..adb065b229a 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_flush.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_flush.c
@@ -16,6 +16,7 @@
* limitations under the License.
*/
+#include "fuse_connect.h"
#include "fuse_dfs.h"
#include "fuse_impls.h"
#include "fuse_file_handle.h"
@@ -43,9 +44,7 @@ int dfs_flush(const char *path, struct fuse_file_info *fi) {
assert(fh);
hdfsFile file_handle = (hdfsFile)fh->hdfsFH;
assert(file_handle);
-
- assert(fh->fs);
- if (hdfsFlush(fh->fs, file_handle) != 0) {
+ if (hdfsFlush(hdfsConnGetFs(fh->conn), file_handle) != 0) {
ERROR("Could not flush %lx for %s\n",(long)file_handle, path);
return -EIO;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_getattr.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_getattr.c
index 56f634e71bb..2e435185ec1 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_getattr.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_getattr.c
@@ -23,22 +23,27 @@
int dfs_getattr(const char *path, struct stat *st)
{
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
+ int ret;
+ hdfsFileInfo *info;
+
TRACE1("getattr", path)
-
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
-
assert(dfs);
assert(path);
assert(st);
- hdfsFS fs = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (NULL == fs) {
- ERROR("Could not connect to %s:%d", dfs->nn_uri, dfs->nn_port);
- return -EIO;
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto cleanup;
}
-
- int ret = 0;
- hdfsFileInfo *info = hdfsGetPathInfo(fs,path);
+ fs = hdfsConnGetFs(conn);
+
+ info = hdfsGetPathInfo(fs,path);
if (NULL == info) {
ret = -ENOENT;
goto cleanup;
@@ -63,9 +68,8 @@ int dfs_getattr(const char *path, struct stat *st)
hdfsFreeFileInfo(info,1);
cleanup:
- if (doDisconnect(fs)) {
- ERROR("Could not disconnect from filesystem");
- ret = -EIO;
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_mkdir.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_mkdir.c
index d0624afa1c2..3aef108109b 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_mkdir.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_mkdir.c
@@ -23,9 +23,12 @@
int dfs_mkdir(const char *path, mode_t mode)
{
- TRACE1("mkdir", path)
-
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ int ret;
+
+ TRACE1("mkdir", path)
assert(path);
assert(dfs);
@@ -41,29 +44,32 @@ int dfs_mkdir(const char *path, mode_t mode)
return -EACCES;
}
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect");
- return -EIO;
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto cleanup;
}
+ fs = hdfsConnGetFs(conn);
// In theory the create and chmod should be atomic.
- int ret = 0;
- if (hdfsCreateDirectory(userFS, path)) {
+ if (hdfsCreateDirectory(fs, path)) {
ERROR("HDFS could not create directory %s", path);
ret = (errno > 0) ? -errno : -EIO;
goto cleanup;
}
- if (hdfsChmod(userFS, path, (short)mode)) {
+ if (hdfsChmod(fs, path, (short)mode)) {
ERROR("Could not chmod %s to %d", path, (int)mode);
ret = (errno > 0) ? -errno : -EIO;
}
+ ret = 0;
cleanup:
- if (doDisconnect(userFS)) {
- ret = -EIO;
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_open.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_open.c
index 071590aa371..ecd772f63f7 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_open.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_open.c
@@ -21,38 +21,45 @@
#include "fuse_connect.h"
#include "fuse_file_handle.h"
+#include
+#include
+
int dfs_open(const char *path, struct fuse_file_info *fi)
{
- TRACE1("open", path)
-
+ hdfsFS fs = NULL;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ dfs_fh *fh = NULL;
+ int mutexInit = 0, ret;
+
+ TRACE1("open", path)
// check params and the context var
assert(path);
assert('/' == *path);
assert(dfs);
- int ret = 0;
-
// 0x8000 is always passed in and hadoop doesn't like it, so killing it here
// bugbug figure out what this flag is and report problem to Hadoop JIRA
int flags = (fi->flags & 0x7FFF);
// retrieve dfs specific data
- dfs_fh *fh = (dfs_fh*)calloc(1, sizeof (dfs_fh));
- if (fh == NULL) {
+ fh = (dfs_fh*)calloc(1, sizeof (dfs_fh));
+ if (!fh) {
ERROR("Malloc of new file handle failed");
- return -EIO;
+ ret = -EIO;
+ goto error;
}
-
- fh->fs = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (fh->fs == NULL) {
- ERROR("Could not connect to dfs");
- return -EIO;
+ ret = fuseConnectAsThreadUid(&fh->conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto error;
}
+ fs = hdfsConnGetFs(fh->conn);
if (flags & O_RDWR) {
- hdfsFileInfo *info = hdfsGetPathInfo(fh->fs,path);
+ hdfsFileInfo *info = hdfsGetPathInfo(fs, path);
if (info == NULL) {
// File does not exist (maybe?); interpret it as a O_WRONLY
// If the actual error was something else, we'll get it again when
@@ -66,15 +73,23 @@ int dfs_open(const char *path, struct fuse_file_info *fi)
}
}
- if ((fh->hdfsFH = hdfsOpenFile(fh->fs, path, flags, 0, 0, 0)) == NULL) {
+ if ((fh->hdfsFH = hdfsOpenFile(fs, path, flags, 0, 0, 0)) == NULL) {
ERROR("Could not open file %s (errno=%d)", path, errno);
if (errno == 0 || errno == EINTERNAL) {
- return -EIO;
+ ret = -EIO;
+ goto error;
}
- return -errno;
+ ret = -errno;
+ goto error;
}
- pthread_mutex_init(&fh->mutex, NULL);
+ ret = pthread_mutex_init(&fh->mutex, NULL);
+ if (ret) {
+ fprintf(stderr, "dfs_open: error initializing mutex: error %d\n", ret);
+ ret = -EIO;
+ goto error;
+ }
+ mutexInit = 1;
if (fi->flags & O_WRONLY || fi->flags & O_CREAT) {
fh->buf = NULL;
@@ -84,11 +99,27 @@ int dfs_open(const char *path, struct fuse_file_info *fi)
if (NULL == fh->buf) {
ERROR("Could not allocate memory for a read for file %s\n", path);
ret = -EIO;
+ goto error;
}
fh->buffersStartOffset = 0;
fh->bufferSize = 0;
}
fi->fh = (uint64_t)fh;
+ return 0;
+error:
+ if (fh) {
+ if (mutexInit) {
+ pthread_mutex_destroy(&fh->mutex);
+ }
+ free(fh->buf);
+ if (fh->hdfsFH) {
+ hdfsCloseFile(fs, fh->hdfsFH);
+ }
+ if (fh->conn) {
+ hdfsConnRelease(fh->conn);
+ }
+ free(fh);
+ }
return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_read.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_read.c
index 52092616c06..feade454035 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_read.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_read.c
@@ -16,9 +16,10 @@
* limitations under the License.
*/
+#include "fuse_connect.h"
#include "fuse_dfs.h"
-#include "fuse_impls.h"
#include "fuse_file_handle.h"
+#include "fuse_impls.h"
static size_t min(const size_t x, const size_t y) {
return x < y ? x : y;
@@ -48,9 +49,9 @@ int dfs_read(const char *path, char *buf, size_t size, off_t offset,
assert(fi);
dfs_fh *fh = (dfs_fh*)fi->fh;
+ hdfsFS fs = hdfsConnGetFs(fh->conn);
assert(fh != NULL);
- assert(fh->fs != NULL);
assert(fh->hdfsFH != NULL);
// special case this as simplifies the rest of the logic to know the caller wanted > 0 bytes
@@ -61,7 +62,7 @@ int dfs_read(const char *path, char *buf, size_t size, off_t offset,
if ( size >= dfs->rdbuffer_size) {
int num_read;
size_t total_read = 0;
- while (size - total_read > 0 && (num_read = hdfsPread(fh->fs, fh->hdfsFH, offset + total_read, buf + total_read, size - total_read)) > 0) {
+ while (size - total_read > 0 && (num_read = hdfsPread(fs, fh->hdfsFH, offset + total_read, buf + total_read, size - total_read)) > 0) {
total_read += num_read;
}
// if there was an error before satisfying the current read, this logic declares it an error
@@ -98,7 +99,7 @@ int dfs_read(const char *path, char *buf, size_t size, off_t offset,
size_t total_read = 0;
while (dfs->rdbuffer_size - total_read > 0 &&
- (num_read = hdfsPread(fh->fs, fh->hdfsFH, offset + total_read, fh->buf + total_read, dfs->rdbuffer_size - total_read)) > 0) {
+ (num_read = hdfsPread(fs, fh->hdfsFH, offset + total_read, fh->buf + total_read, dfs->rdbuffer_size - total_read)) > 0) {
total_read += num_read;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_readdir.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_readdir.c
index f6fe48b6ad9..326f5730509 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_readdir.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_readdir.c
@@ -24,25 +24,31 @@
int dfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
{
- TRACE1("readdir", path)
+ int ret;
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ TRACE1("readdir", path)
+
assert(dfs);
assert(path);
assert(buf);
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect");
- return -EIO;
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto cleanup;
}
+ fs = hdfsConnGetFs(conn);
// Read dirents. Calling a variant that just returns the final path
// component (HDFS-975) would save us from parsing it out below.
int numEntries = 0;
- hdfsFileInfo *info = hdfsListDirectory(userFS, path, &numEntries);
+ hdfsFileInfo *info = hdfsListDirectory(fs, path, &numEntries);
- int ret = 0;
// NULL means either the directory doesn't exist or maybe IO error.
if (NULL == info) {
ret = (errno > 0) ? -errno : -ENOENT;
@@ -106,11 +112,11 @@ int dfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
}
// free the info pointers
hdfsFreeFileInfo(info,numEntries);
+ ret = 0;
cleanup:
- if (doDisconnect(userFS)) {
- ret = -EIO;
- ERROR("Failed to disconnect %d", errno);
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_release.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_release.c
index e15dd572d63..0316de61410 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_release.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_release.c
@@ -52,15 +52,13 @@ int dfs_release (const char *path, struct fuse_file_info *fi) {
assert(fh);
hdfsFile file_handle = (hdfsFile)fh->hdfsFH;
if (NULL != file_handle) {
- if (hdfsCloseFile(fh->fs, file_handle) != 0) {
+ if (hdfsCloseFile(hdfsConnGetFs(fh->conn), file_handle) != 0) {
ERROR("Could not close handle %ld for %s\n",(long)file_handle, path);
ret = -EIO;
}
}
free(fh->buf);
- if (doDisconnect(fh->fs)) {
- ret = -EIO;
- }
+ hdfsConnRelease(fh->conn);
pthread_mutex_destroy(&fh->mutex);
free(fh);
fi->fh = 0;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_rename.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_rename.c
index bbb0462b038..415539f0abc 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_rename.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_rename.c
@@ -23,10 +23,12 @@
int dfs_rename(const char *from, const char *to)
{
- TRACE1("rename", from)
-
- // retrieve dfs specific data
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ int ret;
+
+ TRACE1("rename", from)
// check params and the context var
assert(from);
@@ -46,23 +48,24 @@ int dfs_rename(const char *from, const char *to)
return -EACCES;
}
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect");
- return -EIO;
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto cleanup;
}
-
- int ret = 0;
- if (hdfsRename(userFS, from, to)) {
+ fs = hdfsConnGetFs(conn);
+ if (hdfsRename(fs, from, to)) {
ERROR("Rename %s to %s failed", from, to);
ret = (errno > 0) ? -errno : -EIO;
goto cleanup;
}
+ ret = 0;
cleanup:
- if (doDisconnect(userFS)) {
- ret = -EIO;
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
-
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_rmdir.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_rmdir.c
index 259040fe9fc..f79562a8b50 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_rmdir.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_rmdir.c
@@ -25,9 +25,14 @@ extern const char *const TrashPrefixDir;
int dfs_rmdir(const char *path)
{
- TRACE1("rmdir", path)
-
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
+ int ret;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ int numEntries = 0;
+ hdfsFileInfo *info = NULL;
+
+ TRACE1("rmdir", path)
assert(path);
assert(dfs);
@@ -35,42 +40,43 @@ int dfs_rmdir(const char *path)
if (is_protected(path)) {
ERROR("Trying to delete protected directory %s", path);
- return -EACCES;
+ ret = -EACCES;
+ goto cleanup;
}
if (dfs->read_only) {
ERROR("HDFS configured read-only, cannot delete directory %s", path);
- return -EACCES;
+ ret = -EACCES;
+ goto cleanup;
}
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect");
- return -EIO;
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto cleanup;
}
-
- int ret = 0;
- int numEntries = 0;
- hdfsFileInfo *info = hdfsListDirectory(userFS,path,&numEntries);
-
- if (info) {
- hdfsFreeFileInfo(info, numEntries);
- }
-
+ fs = hdfsConnGetFs(conn);
+ info = hdfsListDirectory(fs, path, &numEntries);
if (numEntries) {
ret = -ENOTEMPTY;
goto cleanup;
}
- if (hdfsDeleteWithTrash(userFS, path, dfs->usetrash)) {
+ if (hdfsDeleteWithTrash(fs, path, dfs->usetrash)) {
ERROR("Error trying to delete directory %s", path);
ret = -EIO;
goto cleanup;
}
+ ret = 0;
cleanup:
- if (doDisconnect(userFS)) {
- ret = -EIO;
+ if (info) {
+ hdfsFreeFileInfo(info, numEntries);
+ }
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_statfs.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_statfs.c
index c7004a96990..c9306a21270 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_statfs.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_statfs.c
@@ -23,9 +23,12 @@
int dfs_statfs(const char *path, struct statvfs *st)
{
- TRACE1("statfs",path)
-
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ int ret;
+
+ TRACE1("statfs",path)
assert(path);
assert(st);
@@ -33,19 +36,18 @@ int dfs_statfs(const char *path, struct statvfs *st)
memset(st,0,sizeof(struct statvfs));
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect");
- return -EIO;
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto cleanup;
}
+ fs = hdfsConnGetFs(conn);
- const tOffset cap = hdfsGetCapacity(userFS);
- const tOffset used = hdfsGetUsed(userFS);
- const tOffset bsize = hdfsGetDefaultBlockSize(userFS);
-
- if (doDisconnect(userFS)) {
- return -EIO;
- }
+ const tOffset cap = hdfsGetCapacity(fs);
+ const tOffset used = hdfsGetUsed(fs);
+ const tOffset bsize = hdfsGetDefaultBlockSize(fs);
st->f_bsize = bsize;
st->f_frsize = bsize;
@@ -58,6 +60,11 @@ int dfs_statfs(const char *path, struct statvfs *st)
st->f_fsid = 1023;
st->f_flag = ST_RDONLY | ST_NOSUID;
st->f_namemax = 1023;
+ ret = 0;
- return 0;
+cleanup:
+ if (conn) {
+ hdfsConnRelease(conn);
+ }
+ return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_truncate.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_truncate.c
index d09b0c8fa63..bf72ca6bec9 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_truncate.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_truncate.c
@@ -28,10 +28,12 @@
*/
int dfs_truncate(const char *path, off_t size)
{
- TRACE1("truncate", path)
-
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ TRACE1("truncate", path)
+
assert(path);
assert('/' == *path);
assert(dfs);
@@ -45,31 +47,33 @@ int dfs_truncate(const char *path, off_t size)
return ret;
}
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect");
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
ret = -EIO;
goto cleanup;
}
+ fs = hdfsConnGetFs(conn);
int flags = O_WRONLY | O_CREAT;
hdfsFile file;
- if ((file = (hdfsFile)hdfsOpenFile(userFS, path, flags, 0, 0, 0)) == NULL) {
+ if ((file = (hdfsFile)hdfsOpenFile(fs, path, flags, 0, 0, 0)) == NULL) {
ERROR("Could not connect open file %s", path);
ret = -EIO;
goto cleanup;
}
- if (hdfsCloseFile(userFS, file) != 0) {
+ if (hdfsCloseFile(fs, file) != 0) {
ERROR("Could not close file %s", path);
ret = -EIO;
goto cleanup;
}
cleanup:
- if (doDisconnect(userFS)) {
- ret = -EIO;
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_unlink.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_unlink.c
index a3d2034dbbd..102c2cd0f3f 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_unlink.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_unlink.c
@@ -20,44 +20,51 @@
#include "fuse_impls.h"
#include "fuse_connect.h"
#include "fuse_trash.h"
-extern const char *const TrashPrefixDir;
int dfs_unlink(const char *path)
{
- TRACE1("unlink", path)
-
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
int ret = 0;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ TRACE1("unlink", path)
+
assert(path);
assert(dfs);
assert('/' == *path);
if (is_protected(path)) {
ERROR("Trying to delete protected directory %s", path);
- return -EACCES;
+ ret = -EACCES;
+ goto cleanup;
}
if (dfs->read_only) {
ERROR("HDFS configured read-only, cannot create directory %s", path);
- return -EACCES;
+ ret = -EACCES;
+ goto cleanup;
}
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect");
- return -EIO;
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto cleanup;
}
+ fs = hdfsConnGetFs(conn);
- if (hdfsDeleteWithTrash(userFS, path, dfs->usetrash)) {
+ if (hdfsDeleteWithTrash(fs, path, dfs->usetrash)) {
ERROR("Could not delete file %s", path);
ret = (errno > 0) ? -errno : -EIO;
goto cleanup;
}
+ ret = 0;
cleanup:
- if (doDisconnect(userFS)) {
- ret = -EIO;
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_utimens.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_utimens.c
index f9144f8451c..dccff92c3fd 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_utimens.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_utimens.c
@@ -22,10 +22,13 @@
int dfs_utimens(const char *path, const struct timespec ts[2])
{
- TRACE1("utimens", path)
+ struct hdfsConn *conn = NULL;
+ hdfsFS fs;
int ret = 0;
dfs_context *dfs = (dfs_context*)fuse_get_context()->private_data;
+ TRACE1("utimens", path)
+
assert(path);
assert(dfs);
assert('/' == *path);
@@ -33,14 +36,17 @@ int dfs_utimens(const char *path, const struct timespec ts[2])
time_t aTime = ts[0].tv_sec;
time_t mTime = ts[1].tv_sec;
- hdfsFS userFS = doConnectAsUser(dfs->nn_uri, dfs->nn_port);
- if (userFS == NULL) {
- ERROR("Could not connect");
- return -EIO;
+ ret = fuseConnectAsThreadUid(&conn);
+ if (ret) {
+ fprintf(stderr, "fuseConnectAsThreadUid: failed to open a libhdfs "
+ "connection! error %d.\n", ret);
+ ret = -EIO;
+ goto cleanup;
}
+ fs = hdfsConnGetFs(conn);
- if (hdfsUtime(userFS, path, mTime, aTime)) {
- hdfsFileInfo *info = hdfsGetPathInfo(userFS, path);
+ if (hdfsUtime(fs, path, mTime, aTime)) {
+ hdfsFileInfo *info = hdfsGetPathInfo(fs, path);
if (info == NULL) {
ret = (errno > 0) ? -errno : -ENOENT;
goto cleanup;
@@ -54,10 +60,11 @@ int dfs_utimens(const char *path, const struct timespec ts[2])
}
goto cleanup;
}
+ ret = 0;
cleanup:
- if (doDisconnect(userFS)) {
- ret = -EIO;
+ if (conn) {
+ hdfsConnRelease(conn);
}
return ret;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_write.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_write.c
index 8bb0454888d..3090e9e32bd 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_write.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_impls_write.c
@@ -16,6 +16,7 @@
* limitations under the License.
*/
+#include "fuse_connect.h"
#include "fuse_dfs.h"
#include "fuse_impls.h"
#include "fuse_file_handle.h"
@@ -48,15 +49,15 @@ int dfs_write(const char *path, const char *buf, size_t size,
pthread_mutex_lock(&fh->mutex);
tSize length = 0;
- assert(fh->fs);
+ hdfsFS fs = hdfsConnGetFs(fh->conn);
- tOffset cur_offset = hdfsTell(fh->fs, file_handle);
+ tOffset cur_offset = hdfsTell(fs, file_handle);
if (cur_offset != offset) {
ERROR("User trying to random access write to a file %d != %d for %s",
(int)cur_offset, (int)offset, path);
ret = -ENOTSUP;
} else {
- length = hdfsWrite(fh->fs, file_handle, buf, size);
+ length = hdfsWrite(fs, file_handle, buf, size);
if (length <= 0) {
ERROR("Could not write all bytes for %s %d != %d (errno=%d)",
path, length, (int)size, errno);
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_init.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_init.c
index 6c1c0d058a3..aeb5f386a6e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_init.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_init.c
@@ -78,8 +78,20 @@ void init_protectedpaths(dfs_context *dfs) {
dfs->protectedpaths[j] = NULL;
}
+static void dfsPrintOptions(FILE *fp, const struct options *o)
+{
+ fprintf(fp, "[ protected=%s, nn_uri=%s, nn_port=%d, "
+ "debug=%d, read_only=%d, initchecks=%d, "
+ "no_permissions=%d, usetrash=%d, entry_timeout=%d, "
+ "attribute_timeout=%d, rdbuffer_size=%Zd, direct_io=%d ]",
+ (o->protected ? o->protected : "(NULL)"), o->nn_uri, o->nn_port,
+ o->debug, o->read_only, o->initchecks,
+ o->no_permissions, o->usetrash, o->entry_timeout,
+ o->attribute_timeout, o->rdbuffer_size, o->direct_io);
+}
-void *dfs_init(void) {
+void *dfs_init(void)
+{
//
// Create a private struct of data we will pass to fuse here and which
// will then be accessible on every call.
@@ -92,15 +104,15 @@ void *dfs_init(void) {
// initialize the context
dfs->debug = options.debug;
- dfs->nn_uri = options.nn_uri;
- dfs->nn_port = options.nn_port;
dfs->read_only = options.read_only;
dfs->usetrash = options.usetrash;
dfs->protectedpaths = NULL;
dfs->rdbuffer_size = options.rdbuffer_size;
dfs->direct_io = options.direct_io;
- INFO("Mounting. nn_uri=%s, nn_port=%d", dfs->nn_uri, dfs->nn_port);
+ fprintf(stderr, "Mounting with options ");
+ dfsPrintOptions(stderr, &options);
+ fprintf(stderr, "\n");
init_protectedpaths(dfs);
assert(dfs->protectedpaths != NULL);
@@ -109,12 +121,6 @@ void *dfs_init(void) {
DEBUG("dfs->rdbuffersize <= 0 = %ld", dfs->rdbuffer_size);
dfs->rdbuffer_size = 32768;
}
-
- if (0 != allocFsTable()) {
- ERROR("FATAL: could not allocate ");
- exit(1);
- }
-
return (void*)dfs;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/hdfs.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/hdfs.c
index eba2bf1b66e..70468f80c02 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/hdfs.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/hdfs.c
@@ -225,7 +225,7 @@ done:
*
* @return 0 on success; error code otherwise
*/
-static int hadoopConfSet(JNIEnv *env, jobject jConfiguration,
+static int hadoopConfSetStr(JNIEnv *env, jobject jConfiguration,
const char *key, const char *value)
{
int ret;
@@ -283,7 +283,7 @@ static int jStrToCstr(JNIEnv *env, jstring jstr, char **cstr)
return 0;
}
-static int hadoopConfGet(JNIEnv *env, jobject jConfiguration,
+static int hadoopConfGetStr(JNIEnv *env, jobject jConfiguration,
const char *key, char **val)
{
int ret;
@@ -301,7 +301,7 @@ static int hadoopConfGet(JNIEnv *env, jobject jConfiguration,
HADOOP_CONF, "get", JMETHOD1(JPARAM(JAVA_STRING),
JPARAM(JAVA_STRING)), jkey);
if (ret) {
- snprintf(buf, sizeof(buf), "hadoopConfGet(%s)", key);
+ snprintf(buf, sizeof(buf), "hadoopConfGetStr(%s)", key);
ret = errnoFromException(jExc, env, buf);
goto done;
}
@@ -321,7 +321,7 @@ done:
return ret;
}
-int hdfsConfGet(const char *key, char **val)
+int hdfsConfGetStr(const char *key, char **val)
{
JNIEnv *env;
int ret;
@@ -339,19 +339,67 @@ int hdfsConfGet(const char *key, char **val)
ret = EINTERNAL;
goto done;
}
- ret = hadoopConfGet(env, jConfiguration, key, val);
- if (ret)
- goto done;
- ret = 0;
+ ret = hadoopConfGetStr(env, jConfiguration, key, val);
done:
- if (jConfiguration)
- destroyLocalReference(env, jConfiguration);
+ destroyLocalReference(env, jConfiguration);
if (ret)
errno = ret;
return ret;
}
-void hdfsConfFree(char *val)
+static int hadoopConfGetInt(JNIEnv *env, jobject jConfiguration,
+ const char *key, int32_t *val)
+{
+ int ret;
+ jthrowable jExc = NULL;
+ jvalue jVal;
+ jstring jkey = NULL;
+ char buf[1024];
+
+ jkey = (*env)->NewStringUTF(env, key);
+ if (!jkey) {
+ (*env)->ExceptionDescribe(env);
+ return ENOMEM;
+ }
+ ret = invokeMethod(env, &jVal, &jExc, INSTANCE, jConfiguration,
+ HADOOP_CONF, "getInt", JMETHOD2(JPARAM(JAVA_STRING), "I", "I"),
+ jkey, (jint)(*val));
+ destroyLocalReference(env, jkey);
+ if (ret) {
+ snprintf(buf, sizeof(buf), "hadoopConfGetInt(%s)", key);
+ return errnoFromException(jExc, env, buf);
+ }
+ *val = jVal.i;
+ return 0;
+}
+
+int hdfsConfGetInt(const char *key, int32_t *val)
+{
+ JNIEnv *env;
+ int ret;
+ jobject jConfiguration = NULL;
+
+ env = getJNIEnv();
+ if (env == NULL) {
+ ret = EINTERNAL;
+ goto done;
+ }
+ jConfiguration = constructNewObjectOfClass(env, NULL, HADOOP_CONF, "()V");
+ if (jConfiguration == NULL) {
+ fprintf(stderr, "Can't construct instance of class "
+ "org.apache.hadoop.conf.Configuration\n");
+ ret = EINTERNAL;
+ goto done;
+ }
+ ret = hadoopConfGetInt(env, jConfiguration, key, val);
+done:
+ destroyLocalReference(env, jConfiguration);
+ if (ret)
+ errno = ret;
+ return ret;
+}
+
+void hdfsConfStrFree(char *val)
{
free(val);
}
@@ -583,7 +631,7 @@ hdfsFS hdfsBuilderConnect(struct hdfsBuilder *bld)
}
if (bld->kerbTicketCachePath) {
- ret = hadoopConfSet(env, jConfiguration,
+ ret = hadoopConfSetStr(env, jConfiguration,
KERBEROS_TICKET_CACHE_PATH, bld->kerbTicketCachePath);
if (ret)
goto done;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/hdfs.h b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/hdfs.h
index 5c3c6dfe2d7..4f252cd8d0d 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/hdfs.h
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/hdfs.h
@@ -220,21 +220,33 @@ extern "C" {
* Get a configuration string.
*
* @param key The key to find
- * @param val (out param) The value. This will be NULL if the
+ * @param val (out param) The value. This will be set to NULL if the
* key isn't found. You must free this string with
- * hdfsConfFree.
+ * hdfsConfStrFree.
*
* @return 0 on success; nonzero error code otherwise.
* Failure to find the key is not an error.
*/
- int hdfsConfGet(const char *key, char **val);
+ int hdfsConfGetStr(const char *key, char **val);
/**
- * Free a configuration string found with hdfsConfGet.
+ * Get a configuration integer.
*
- * @param val A configuration string obtained from hdfsConfGet
+ * @param key The key to find
+ * @param val (out param) The value. This will NOT be changed if the
+ * key isn't found.
+ *
+ * @return 0 on success; nonzero error code otherwise.
+ * Failure to find the key is not an error.
*/
- void hdfsConfFree(char *val);
+ int hdfsConfGetInt(const char *key, int32_t *val);
+
+ /**
+ * Free a configuration string found with hdfsConfGetStr.
+ *
+ * @param val A configuration string obtained from hdfsConfGetStr
+ */
+ void hdfsConfStrFree(char *val);
/**
* hdfsDisconnect - Disconnect from the hdfs file system.
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/jni_helper.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/jni_helper.c
index 97f92942f9c..d788f6282ee 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/jni_helper.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/libhdfs/jni_helper.c
@@ -412,6 +412,7 @@ char *classNameOfObject(jobject jobj, JNIEnv *env) {
return newstr;
}
+
/**
* Get the global JNI environemnt.
*
@@ -500,6 +501,11 @@ static JNIEnv* getGlobalJNIEnv(void)
"with error: %d\n", rv);
return NULL;
}
+ if (invokeMethod(env, NULL, NULL, STATIC, NULL,
+ "org/apache/hadoop/fs/FileSystem",
+ "loadFileSystems", "()V") != 0) {
+ (*env)->ExceptionDescribe(env);
+ }
}
else {
//Attach this thread to the VM
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/util/tree.h b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/util/tree.h
new file mode 100644
index 00000000000..ac78ee30af5
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/util/tree.h
@@ -0,0 +1,765 @@
+/* $NetBSD: tree.h,v 1.8 2004/03/28 19:38:30 provos Exp $ */
+/* $OpenBSD: tree.h,v 1.7 2002/10/17 21:51:54 art Exp $ */
+/* $FreeBSD: src/sys/sys/tree.h,v 1.9.4.1 2011/09/23 00:51:37 kensmith Exp $ */
+
+/*-
+ * Copyright 2002 Niels Provos
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _SYS_TREE_H_
+#define _SYS_TREE_H_
+
+#include
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure. Every operation
+ * on the tree causes a splay to happen. The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree. On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n). The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute. It fulfills a set of conditions:
+ * - every search path from the root to a leaf consists of the
+ * same number of black nodes,
+ * - each red node (except for the root) has a black parent,
+ * - each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type) \
+struct name { \
+ struct type *sph_root; /* root of the tree */ \
+}
+
+#define SPLAY_INITIALIZER(root) \
+ { NULL }
+
+#define SPLAY_INIT(root) do { \
+ (root)->sph_root = NULL; \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_ENTRY(type) \
+struct { \
+ struct type *spe_left; /* left element */ \
+ struct type *spe_right; /* right element */ \
+}
+
+#define SPLAY_LEFT(elm, field) (elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right
+#define SPLAY_ROOT(head) (head)->sph_root
+#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \
+ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \
+ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
+ (head)->sph_root = tmp; \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \
+ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \
+ SPLAY_LEFT(tmp, field) = (head)->sph_root; \
+ (head)->sph_root = tmp; \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) do { \
+ SPLAY_LEFT(tmp, field) = (head)->sph_root; \
+ tmp = (head)->sph_root; \
+ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) do { \
+ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
+ tmp = (head)->sph_root; \
+ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \
+} while (/*CONSTCOND*/ 0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \
+ SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
+ SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
+ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
+ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
+} while (/*CONSTCOND*/ 0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp) \
+void name##_SPLAY(struct name *, struct type *); \
+void name##_SPLAY_MINMAX(struct name *, int); \
+struct type *name##_SPLAY_INSERT(struct name *, struct type *); \
+struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \
+ \
+/* Finds the node with the same key as elm */ \
+static __inline struct type * \
+name##_SPLAY_FIND(struct name *head, struct type *elm) \
+{ \
+ if (SPLAY_EMPTY(head)) \
+ return(NULL); \
+ name##_SPLAY(head, elm); \
+ if ((cmp)(elm, (head)->sph_root) == 0) \
+ return (head->sph_root); \
+ return (NULL); \
+} \
+ \
+static __inline struct type * \
+name##_SPLAY_NEXT(struct name *head, struct type *elm) \
+{ \
+ name##_SPLAY(head, elm); \
+ if (SPLAY_RIGHT(elm, field) != NULL) { \
+ elm = SPLAY_RIGHT(elm, field); \
+ while (SPLAY_LEFT(elm, field) != NULL) { \
+ elm = SPLAY_LEFT(elm, field); \
+ } \
+ } else \
+ elm = NULL; \
+ return (elm); \
+} \
+ \
+static __inline struct type * \
+name##_SPLAY_MIN_MAX(struct name *head, int val) \
+{ \
+ name##_SPLAY_MINMAX(head, val); \
+ return (SPLAY_ROOT(head)); \
+}
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp) \
+struct type * \
+name##_SPLAY_INSERT(struct name *head, struct type *elm) \
+{ \
+ if (SPLAY_EMPTY(head)) { \
+ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \
+ } else { \
+ int __comp; \
+ name##_SPLAY(head, elm); \
+ __comp = (cmp)(elm, (head)->sph_root); \
+ if(__comp < 0) { \
+ SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
+ SPLAY_RIGHT(elm, field) = (head)->sph_root; \
+ SPLAY_LEFT((head)->sph_root, field) = NULL; \
+ } else if (__comp > 0) { \
+ SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
+ SPLAY_LEFT(elm, field) = (head)->sph_root; \
+ SPLAY_RIGHT((head)->sph_root, field) = NULL; \
+ } else \
+ return ((head)->sph_root); \
+ } \
+ (head)->sph_root = (elm); \
+ return (NULL); \
+} \
+ \
+struct type * \
+name##_SPLAY_REMOVE(struct name *head, struct type *elm) \
+{ \
+ struct type *__tmp; \
+ if (SPLAY_EMPTY(head)) \
+ return (NULL); \
+ name##_SPLAY(head, elm); \
+ if ((cmp)(elm, (head)->sph_root) == 0) { \
+ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \
+ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
+ } else { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
+ name##_SPLAY(head, elm); \
+ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \
+ } \
+ return (elm); \
+ } \
+ return (NULL); \
+} \
+ \
+void \
+name##_SPLAY(struct name *head, struct type *elm) \
+{ \
+ struct type __node, *__left, *__right, *__tmp; \
+ int __comp; \
+\
+ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+ __left = __right = &__node; \
+\
+ while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \
+ if (__comp < 0) { \
+ __tmp = SPLAY_LEFT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if ((cmp)(elm, __tmp) < 0){ \
+ SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+ break; \
+ } \
+ SPLAY_LINKLEFT(head, __right, field); \
+ } else if (__comp > 0) { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if ((cmp)(elm, __tmp) > 0){ \
+ SPLAY_ROTATE_LEFT(head, __tmp, field); \
+ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+ break; \
+ } \
+ SPLAY_LINKRIGHT(head, __left, field); \
+ } \
+ } \
+ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
+} \
+ \
+/* Splay with either the minimum or the maximum element \
+ * Used to find minimum or maximum element in tree. \
+ */ \
+void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+{ \
+ struct type __node, *__left, *__right, *__tmp; \
+\
+ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+ __left = __right = &__node; \
+\
+ while (1) { \
+ if (__comp < 0) { \
+ __tmp = SPLAY_LEFT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if (__comp < 0){ \
+ SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+ break; \
+ } \
+ SPLAY_LINKLEFT(head, __right, field); \
+ } else if (__comp > 0) { \
+ __tmp = SPLAY_RIGHT((head)->sph_root, field); \
+ if (__tmp == NULL) \
+ break; \
+ if (__comp > 0) { \
+ SPLAY_ROTATE_LEFT(head, __tmp, field); \
+ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+ break; \
+ } \
+ SPLAY_LINKRIGHT(head, __left, field); \
+ } \
+ } \
+ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
+}
+
+#define SPLAY_NEGINF -1
+#define SPLAY_INF 1
+
+#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \
+ : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \
+ : name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head) \
+ for ((x) = SPLAY_MIN(name, head); \
+ (x) != NULL; \
+ (x) = SPLAY_NEXT(name, head, x))
+
+/* Macros that define a red-black tree */
+#define RB_HEAD(name, type) \
+struct name { \
+ struct type *rbh_root; /* root of the tree */ \
+}
+
+#define RB_INITIALIZER(root) \
+ { NULL }
+
+#define RB_INIT(root) do { \
+ (root)->rbh_root = NULL; \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_BLACK 0
+#define RB_RED 1
+#define RB_ENTRY(type) \
+struct { \
+ struct type *rbe_left; /* left element */ \
+ struct type *rbe_right; /* right element */ \
+ struct type *rbe_parent; /* parent element */ \
+ int rbe_color; /* node color */ \
+}
+
+#define RB_LEFT(elm, field) (elm)->field.rbe_left
+#define RB_RIGHT(elm, field) (elm)->field.rbe_right
+#define RB_PARENT(elm, field) (elm)->field.rbe_parent
+#define RB_COLOR(elm, field) (elm)->field.rbe_color
+#define RB_ROOT(head) (head)->rbh_root
+#define RB_EMPTY(head) (RB_ROOT(head) == NULL)
+
+#define RB_SET(elm, parent, field) do { \
+ RB_PARENT(elm, field) = parent; \
+ RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \
+ RB_COLOR(elm, field) = RB_RED; \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_SET_BLACKRED(black, red, field) do { \
+ RB_COLOR(black, field) = RB_BLACK; \
+ RB_COLOR(red, field) = RB_RED; \
+} while (/*CONSTCOND*/ 0)
+
+#ifndef RB_AUGMENT
+#define RB_AUGMENT(x) do {} while (0)
+#endif
+
+#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \
+ (tmp) = RB_RIGHT(elm, field); \
+ if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \
+ RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \
+ } \
+ RB_AUGMENT(elm); \
+ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
+ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
+ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
+ else \
+ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+ } else \
+ (head)->rbh_root = (tmp); \
+ RB_LEFT(tmp, field) = (elm); \
+ RB_PARENT(elm, field) = (tmp); \
+ RB_AUGMENT(tmp); \
+ if ((RB_PARENT(tmp, field))) \
+ RB_AUGMENT(RB_PARENT(tmp, field)); \
+} while (/*CONSTCOND*/ 0)
+
+#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \
+ (tmp) = RB_LEFT(elm, field); \
+ if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \
+ RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \
+ } \
+ RB_AUGMENT(elm); \
+ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
+ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
+ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
+ else \
+ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+ } else \
+ (head)->rbh_root = (tmp); \
+ RB_RIGHT(tmp, field) = (elm); \
+ RB_PARENT(elm, field) = (tmp); \
+ RB_AUGMENT(tmp); \
+ if ((RB_PARENT(tmp, field))) \
+ RB_AUGMENT(RB_PARENT(tmp, field)); \
+} while (/*CONSTCOND*/ 0)
+
+/* Generates prototypes and inline functions */
+#define RB_PROTOTYPE(name, type, field, cmp) \
+ RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \
+ RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __unused static)
+#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \
+attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \
+attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
+attr struct type *name##_RB_REMOVE(struct name *, struct type *); \
+attr struct type *name##_RB_INSERT(struct name *, struct type *); \
+attr struct type *name##_RB_FIND(struct name *, struct type *); \
+attr struct type *name##_RB_NFIND(struct name *, struct type *); \
+attr struct type *name##_RB_NEXT(struct type *); \
+attr struct type *name##_RB_PREV(struct type *); \
+attr struct type *name##_RB_MINMAX(struct name *, int); \
+ \
+
+/* Main rb operation.
+ * Moves node close to the key of elm to top
+ */
+#define RB_GENERATE(name, type, field, cmp) \
+ RB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define RB_GENERATE_STATIC(name, type, field, cmp) \
+ RB_GENERATE_INTERNAL(name, type, field, cmp, __unused static)
+#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \
+attr void \
+name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \
+{ \
+ struct type *parent, *gparent, *tmp; \
+ while ((parent = RB_PARENT(elm, field)) != NULL && \
+ RB_COLOR(parent, field) == RB_RED) { \
+ gparent = RB_PARENT(parent, field); \
+ if (parent == RB_LEFT(gparent, field)) { \
+ tmp = RB_RIGHT(gparent, field); \
+ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
+ RB_COLOR(tmp, field) = RB_BLACK; \
+ RB_SET_BLACKRED(parent, gparent, field);\
+ elm = gparent; \
+ continue; \
+ } \
+ if (RB_RIGHT(parent, field) == elm) { \
+ RB_ROTATE_LEFT(head, parent, tmp, field);\
+ tmp = parent; \
+ parent = elm; \
+ elm = tmp; \
+ } \
+ RB_SET_BLACKRED(parent, gparent, field); \
+ RB_ROTATE_RIGHT(head, gparent, tmp, field); \
+ } else { \
+ tmp = RB_LEFT(gparent, field); \
+ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
+ RB_COLOR(tmp, field) = RB_BLACK; \
+ RB_SET_BLACKRED(parent, gparent, field);\
+ elm = gparent; \
+ continue; \
+ } \
+ if (RB_LEFT(parent, field) == elm) { \
+ RB_ROTATE_RIGHT(head, parent, tmp, field);\
+ tmp = parent; \
+ parent = elm; \
+ elm = tmp; \
+ } \
+ RB_SET_BLACKRED(parent, gparent, field); \
+ RB_ROTATE_LEFT(head, gparent, tmp, field); \
+ } \
+ } \
+ RB_COLOR(head->rbh_root, field) = RB_BLACK; \
+} \
+ \
+attr void \
+name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
+{ \
+ struct type *tmp; \
+ while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \
+ elm != RB_ROOT(head)) { \
+ if (RB_LEFT(parent, field) == elm) { \
+ tmp = RB_RIGHT(parent, field); \
+ if (RB_COLOR(tmp, field) == RB_RED) { \
+ RB_SET_BLACKRED(tmp, parent, field); \
+ RB_ROTATE_LEFT(head, parent, tmp, field);\
+ tmp = RB_RIGHT(parent, field); \
+ } \
+ if ((RB_LEFT(tmp, field) == NULL || \
+ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+ (RB_RIGHT(tmp, field) == NULL || \
+ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+ RB_COLOR(tmp, field) = RB_RED; \
+ elm = parent; \
+ parent = RB_PARENT(elm, field); \
+ } else { \
+ if (RB_RIGHT(tmp, field) == NULL || \
+ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
+ struct type *oleft; \
+ if ((oleft = RB_LEFT(tmp, field)) \
+ != NULL) \
+ RB_COLOR(oleft, field) = RB_BLACK;\
+ RB_COLOR(tmp, field) = RB_RED; \
+ RB_ROTATE_RIGHT(head, tmp, oleft, field);\
+ tmp = RB_RIGHT(parent, field); \
+ } \
+ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+ RB_COLOR(parent, field) = RB_BLACK; \
+ if (RB_RIGHT(tmp, field)) \
+ RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
+ RB_ROTATE_LEFT(head, parent, tmp, field);\
+ elm = RB_ROOT(head); \
+ break; \
+ } \
+ } else { \
+ tmp = RB_LEFT(parent, field); \
+ if (RB_COLOR(tmp, field) == RB_RED) { \
+ RB_SET_BLACKRED(tmp, parent, field); \
+ RB_ROTATE_RIGHT(head, parent, tmp, field);\
+ tmp = RB_LEFT(parent, field); \
+ } \
+ if ((RB_LEFT(tmp, field) == NULL || \
+ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+ (RB_RIGHT(tmp, field) == NULL || \
+ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+ RB_COLOR(tmp, field) = RB_RED; \
+ elm = parent; \
+ parent = RB_PARENT(elm, field); \
+ } else { \
+ if (RB_LEFT(tmp, field) == NULL || \
+ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
+ struct type *oright; \
+ if ((oright = RB_RIGHT(tmp, field)) \
+ != NULL) \
+ RB_COLOR(oright, field) = RB_BLACK;\
+ RB_COLOR(tmp, field) = RB_RED; \
+ RB_ROTATE_LEFT(head, tmp, oright, field);\
+ tmp = RB_LEFT(parent, field); \
+ } \
+ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+ RB_COLOR(parent, field) = RB_BLACK; \
+ if (RB_LEFT(tmp, field)) \
+ RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
+ RB_ROTATE_RIGHT(head, parent, tmp, field);\
+ elm = RB_ROOT(head); \
+ break; \
+ } \
+ } \
+ } \
+ if (elm) \
+ RB_COLOR(elm, field) = RB_BLACK; \
+} \
+ \
+attr struct type * \
+name##_RB_REMOVE(struct name *head, struct type *elm) \
+{ \
+ struct type *child, *parent, *old = elm; \
+ int color; \
+ if (RB_LEFT(elm, field) == NULL) \
+ child = RB_RIGHT(elm, field); \
+ else if (RB_RIGHT(elm, field) == NULL) \
+ child = RB_LEFT(elm, field); \
+ else { \
+ struct type *left; \
+ elm = RB_RIGHT(elm, field); \
+ while ((left = RB_LEFT(elm, field)) != NULL) \
+ elm = left; \
+ child = RB_RIGHT(elm, field); \
+ parent = RB_PARENT(elm, field); \
+ color = RB_COLOR(elm, field); \
+ if (child) \
+ RB_PARENT(child, field) = parent; \
+ if (parent) { \
+ if (RB_LEFT(parent, field) == elm) \
+ RB_LEFT(parent, field) = child; \
+ else \
+ RB_RIGHT(parent, field) = child; \
+ RB_AUGMENT(parent); \
+ } else \
+ RB_ROOT(head) = child; \
+ if (RB_PARENT(elm, field) == old) \
+ parent = elm; \
+ (elm)->field = (old)->field; \
+ if (RB_PARENT(old, field)) { \
+ if (RB_LEFT(RB_PARENT(old, field), field) == old)\
+ RB_LEFT(RB_PARENT(old, field), field) = elm;\
+ else \
+ RB_RIGHT(RB_PARENT(old, field), field) = elm;\
+ RB_AUGMENT(RB_PARENT(old, field)); \
+ } else \
+ RB_ROOT(head) = elm; \
+ RB_PARENT(RB_LEFT(old, field), field) = elm; \
+ if (RB_RIGHT(old, field)) \
+ RB_PARENT(RB_RIGHT(old, field), field) = elm; \
+ if (parent) { \
+ left = parent; \
+ do { \
+ RB_AUGMENT(left); \
+ } while ((left = RB_PARENT(left, field)) != NULL); \
+ } \
+ goto color; \
+ } \
+ parent = RB_PARENT(elm, field); \
+ color = RB_COLOR(elm, field); \
+ if (child) \
+ RB_PARENT(child, field) = parent; \
+ if (parent) { \
+ if (RB_LEFT(parent, field) == elm) \
+ RB_LEFT(parent, field) = child; \
+ else \
+ RB_RIGHT(parent, field) = child; \
+ RB_AUGMENT(parent); \
+ } else \
+ RB_ROOT(head) = child; \
+color: \
+ if (color == RB_BLACK) \
+ name##_RB_REMOVE_COLOR(head, parent, child); \
+ return (old); \
+} \
+ \
+/* Inserts a node into the RB tree */ \
+attr struct type * \
+name##_RB_INSERT(struct name *head, struct type *elm) \
+{ \
+ struct type *tmp; \
+ struct type *parent = NULL; \
+ int comp = 0; \
+ tmp = RB_ROOT(head); \
+ while (tmp) { \
+ parent = tmp; \
+ comp = (cmp)(elm, parent); \
+ if (comp < 0) \
+ tmp = RB_LEFT(tmp, field); \
+ else if (comp > 0) \
+ tmp = RB_RIGHT(tmp, field); \
+ else \
+ return (tmp); \
+ } \
+ RB_SET(elm, parent, field); \
+ if (parent != NULL) { \
+ if (comp < 0) \
+ RB_LEFT(parent, field) = elm; \
+ else \
+ RB_RIGHT(parent, field) = elm; \
+ RB_AUGMENT(parent); \
+ } else \
+ RB_ROOT(head) = elm; \
+ name##_RB_INSERT_COLOR(head, elm); \
+ return (NULL); \
+} \
+ \
+/* Finds the node with the same key as elm */ \
+attr struct type * \
+name##_RB_FIND(struct name *head, struct type *elm) \
+{ \
+ struct type *tmp = RB_ROOT(head); \
+ int comp; \
+ while (tmp) { \
+ comp = cmp(elm, tmp); \
+ if (comp < 0) \
+ tmp = RB_LEFT(tmp, field); \
+ else if (comp > 0) \
+ tmp = RB_RIGHT(tmp, field); \
+ else \
+ return (tmp); \
+ } \
+ return (NULL); \
+} \
+ \
+/* Finds the first node greater than or equal to the search key */ \
+attr struct type * \
+name##_RB_NFIND(struct name *head, struct type *elm) \
+{ \
+ struct type *tmp = RB_ROOT(head); \
+ struct type *res = NULL; \
+ int comp; \
+ while (tmp) { \
+ comp = cmp(elm, tmp); \
+ if (comp < 0) { \
+ res = tmp; \
+ tmp = RB_LEFT(tmp, field); \
+ } \
+ else if (comp > 0) \
+ tmp = RB_RIGHT(tmp, field); \
+ else \
+ return (tmp); \
+ } \
+ return (res); \
+} \
+ \
+/* ARGSUSED */ \
+attr struct type * \
+name##_RB_NEXT(struct type *elm) \
+{ \
+ if (RB_RIGHT(elm, field)) { \
+ elm = RB_RIGHT(elm, field); \
+ while (RB_LEFT(elm, field)) \
+ elm = RB_LEFT(elm, field); \
+ } else { \
+ if (RB_PARENT(elm, field) && \
+ (elm == RB_LEFT(RB_PARENT(elm, field), field))) \
+ elm = RB_PARENT(elm, field); \
+ else { \
+ while (RB_PARENT(elm, field) && \
+ (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
+ elm = RB_PARENT(elm, field); \
+ elm = RB_PARENT(elm, field); \
+ } \
+ } \
+ return (elm); \
+} \
+ \
+/* ARGSUSED */ \
+attr struct type * \
+name##_RB_PREV(struct type *elm) \
+{ \
+ if (RB_LEFT(elm, field)) { \
+ elm = RB_LEFT(elm, field); \
+ while (RB_RIGHT(elm, field)) \
+ elm = RB_RIGHT(elm, field); \
+ } else { \
+ if (RB_PARENT(elm, field) && \
+ (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \
+ elm = RB_PARENT(elm, field); \
+ else { \
+ while (RB_PARENT(elm, field) && \
+ (elm == RB_LEFT(RB_PARENT(elm, field), field)))\
+ elm = RB_PARENT(elm, field); \
+ elm = RB_PARENT(elm, field); \
+ } \
+ } \
+ return (elm); \
+} \
+ \
+attr struct type * \
+name##_RB_MINMAX(struct name *head, int val) \
+{ \
+ struct type *tmp = RB_ROOT(head); \
+ struct type *parent = NULL; \
+ while (tmp) { \
+ parent = tmp; \
+ if (val < 0) \
+ tmp = RB_LEFT(tmp, field); \
+ else \
+ tmp = RB_RIGHT(tmp, field); \
+ } \
+ return (parent); \
+}
+
+#define RB_NEGINF -1
+#define RB_INF 1
+
+#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y)
+#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y)
+#define RB_FIND(name, x, y) name##_RB_FIND(x, y)
+#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y)
+#define RB_NEXT(name, x, y) name##_RB_NEXT(y)
+#define RB_PREV(name, x, y) name##_RB_PREV(y)
+#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF)
+#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF)
+
+#define RB_FOREACH(x, name, head) \
+ for ((x) = RB_MIN(name, head); \
+ (x) != NULL; \
+ (x) = name##_RB_NEXT(x))
+
+#define RB_FOREACH_FROM(x, name, y) \
+ for ((x) = (y); \
+ ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
+ (x) = (y))
+
+#define RB_FOREACH_SAFE(x, name, head, y) \
+ for ((x) = RB_MIN(name, head); \
+ ((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
+ (x) = (y))
+
+#define RB_FOREACH_REVERSE(x, name, head) \
+ for ((x) = RB_MAX(name, head); \
+ (x) != NULL; \
+ (x) = name##_RB_PREV(x))
+
+#define RB_FOREACH_REVERSE_FROM(x, name, y) \
+ for ((x) = (y); \
+ ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
+ (x) = (y))
+
+#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \
+ for ((x) = RB_MAX(name, head); \
+ ((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
+ (x) = (y))
+
+#endif /* _SYS_TREE_H_ */
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml b/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
index 27f3b9e522f..00c5e8bc446 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
@@ -909,4 +909,25 @@
+
+ hadoop.fuse.connection.timeout
+ 300
+
+ The minimum number of seconds that we'll cache libhdfs connection objects
+ in fuse_dfs. Lower values will result in lower memory consumption; higher
+ values may speed up access by avoiding the overhead of creating new
+ connection objects.
+
+
+
+
+ hadoop.fuse.timer.period
+ 5
+
+ The number of seconds between cache expiry checks in fuse_dfs. Lower values
+ will result in fuse_dfs noticing changes to Kerberos ticket caches more
+ quickly.
+
+
+
From d45922de2c5645e11339b94e4c31935ead66fefc Mon Sep 17 00:00:00 2001
From: Thomas Graves
Date: Fri, 20 Jul 2012 20:18:48 +0000
Subject: [PATCH 03/39] svn merge --change -1363454 for reverting
MAPREDUCE-4423
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1363935 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-mapreduce-project/CHANGES.txt | 3 -
.../hadoop/mapreduce/task/reduce/Fetcher.java | 62 ++++-----
.../mapreduce/task/reduce/TestFetcher.java | 121 ------------------
3 files changed, 22 insertions(+), 164 deletions(-)
delete mode 100644 hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/task/reduce/TestFetcher.java
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index 5df9c136f7b..8639a6f7625 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -739,9 +739,6 @@ Release 0.23.3 - UNRELEASED
MAPREDUCE-4448. Fix NM crash during app cleanup if aggregation didn't
init. (Jason Lowe via daryn)
- MAPREDUCE-4423. Potential infinite fetching of map output (Robert Evans
- via tgraves)
-
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
index 7852da93a52..f3e7fd61c27 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
@@ -49,8 +49,7 @@ import org.apache.hadoop.mapreduce.task.reduce.MapOutput.Type;
import org.apache.hadoop.util.Progressable;
import org.apache.hadoop.util.ReflectionUtils;
-import com.google.common.annotations.VisibleForTesting;
-
+@SuppressWarnings({"deprecation"})
class Fetcher extends Thread {
private static final Log LOG = LogFactory.getLog(Fetcher.class);
@@ -176,18 +175,13 @@ class Fetcher extends Thread {
}
}
- @VisibleForTesting
- protected HttpURLConnection openConnection(URL url) throws IOException {
- return (HttpURLConnection)url.openConnection();
- }
-
/**
* The crux of the matter...
*
* @param host {@link MapHost} from which we need to
* shuffle available map-outputs.
*/
- protected void copyFromHost(MapHost host) throws IOException {
+ private void copyFromHost(MapHost host) throws IOException {
// Get completed maps on 'host'
List maps = scheduler.getMapsForHost(host);
@@ -197,11 +191,9 @@ class Fetcher extends Thread {
return;
}
- if(LOG.isDebugEnabled()) {
- LOG.debug("Fetcher " + id + " going to fetch from " + host);
- for (TaskAttemptID tmp: maps) {
- LOG.debug(tmp);
- }
+ LOG.debug("Fetcher " + id + " going to fetch from " + host);
+ for (TaskAttemptID tmp: maps) {
+ LOG.debug(tmp);
}
// List of maps to be fetched yet
@@ -213,7 +205,7 @@ class Fetcher extends Thread {
try {
URL url = getMapOutputURL(host, maps);
- HttpURLConnection connection = openConnection(url);
+ HttpURLConnection connection = (HttpURLConnection)url.openConnection();
// generate hash of the url
String msgToEncode = SecureShuffleUtils.buildMsgFrom(url);
@@ -274,24 +266,17 @@ class Fetcher extends Thread {
try {
// Loop through available map-outputs and fetch them
- // On any error, faildTasks is not null and we exit
- // after putting back the remaining maps to the
- // yet_to_be_fetched list and marking the failed tasks.
- TaskAttemptID[] failedTasks = null;
- while (!remaining.isEmpty() && failedTasks == null) {
- failedTasks = copyMapOutput(host, input, remaining);
- }
-
- if(failedTasks != null) {
- for(TaskAttemptID left: failedTasks) {
- scheduler.copyFailed(left, host, true);
- }
+ // On any error, good becomes false and we exit after putting back
+ // the remaining maps to the yet_to_be_fetched list
+ boolean good = true;
+ while (!remaining.isEmpty() && good) {
+ good = copyMapOutput(host, input, remaining);
}
IOUtils.cleanup(LOG, input);
// Sanity check
- if (failedTasks == null && !remaining.isEmpty()) {
+ if (good && !remaining.isEmpty()) {
throw new IOException("server didn't return all expected map outputs: "
+ remaining.size() + " left.");
}
@@ -300,9 +285,10 @@ class Fetcher extends Thread {
scheduler.putBackKnownMapOutput(host, left);
}
}
- }
+
+ }
- private TaskAttemptID[] copyMapOutput(MapHost host,
+ private boolean copyMapOutput(MapHost host,
DataInputStream input,
Set remaining) {
MapOutput mapOutput = null;
@@ -324,15 +310,14 @@ class Fetcher extends Thread {
} catch (IllegalArgumentException e) {
badIdErrs.increment(1);
LOG.warn("Invalid map id ", e);
- //Don't know which one was bad, so consider all of them as bad
- return remaining.toArray(new TaskAttemptID[remaining.size()]);
+ return false;
}
// Do some basic sanity verification
if (!verifySanity(compressedLength, decompressedLength, forReduce,
remaining, mapId)) {
- return new TaskAttemptID[] {mapId};
+ return false;
}
LOG.debug("header: " + mapId + ", len: " + compressedLength +
@@ -344,7 +329,7 @@ class Fetcher extends Thread {
// Check if we can shuffle *now* ...
if (mapOutput.getType() == Type.WAIT) {
LOG.info("fetcher#" + id + " - MergerManager returned Status.WAIT ...");
- return new TaskAttemptID[] {mapId};
+ return false;
}
// Go!
@@ -366,18 +351,14 @@ class Fetcher extends Thread {
// Note successful shuffle
remaining.remove(mapId);
metrics.successFetch();
- return null;
+ return true;
} catch (IOException ioe) {
ioErrs.increment(1);
if (mapId == null || mapOutput == null) {
LOG.info("fetcher#" + id + " failed to read map header" +
mapId + " decomp: " +
decompressedLength + ", " + compressedLength, ioe);
- if(mapId == null) {
- return remaining.toArray(new TaskAttemptID[remaining.size()]);
- } else {
- return new TaskAttemptID[] {mapId};
- }
+ return false;
}
LOG.info("Failed to shuffle output of " + mapId +
@@ -385,8 +366,9 @@ class Fetcher extends Thread {
// Inform the shuffle-scheduler
mapOutput.abort();
+ scheduler.copyFailed(mapId, host, true);
metrics.failedFetch();
- return new TaskAttemptID[] {mapId};
+ return false;
}
}
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/task/reduce/TestFetcher.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/task/reduce/TestFetcher.java
deleted file mode 100644
index 7f7c3f50b87..00000000000
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/task/reduce/TestFetcher.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * 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.mapreduce.task.reduce;
-
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.ArrayList;
-
-import javax.crypto.SecretKey;
-
-import org.apache.hadoop.io.Text;
-import org.apache.hadoop.mapred.Counters;
-import org.apache.hadoop.mapred.JobConf;
-import org.apache.hadoop.mapred.Reporter;
-import org.apache.hadoop.mapreduce.TaskAttemptID;
-import org.apache.hadoop.mapreduce.security.SecureShuffleUtils;
-import org.apache.hadoop.mapreduce.security.token.JobTokenSecretManager;
-import org.junit.Test;
-
-/**
- * Test that the Fetcher does what we expect it to.
- */
-public class TestFetcher {
-
- public static class FakeFetcher extends Fetcher {
-
- private HttpURLConnection connection;
-
- public FakeFetcher(JobConf job, TaskAttemptID reduceId,
- ShuffleScheduler scheduler, MergeManager merger, Reporter reporter,
- ShuffleClientMetrics metrics, ExceptionReporter exceptionReporter,
- SecretKey jobTokenSecret, HttpURLConnection connection) {
- super(job, reduceId, scheduler, merger, reporter, metrics, exceptionReporter,
- jobTokenSecret);
- this.connection = connection;
- }
-
- @Override
- protected HttpURLConnection openConnection(URL url) throws IOException {
- if(connection != null) {
- return connection;
- }
- return super.openConnection(url);
- }
- }
-
- @SuppressWarnings("unchecked")
- @Test
- public void testCopyFromHostBogusHeader() throws Exception {
- JobConf job = new JobConf();
- TaskAttemptID id = TaskAttemptID.forName("attempt_0_1_r_1_1");
- ShuffleScheduler ss = mock(ShuffleScheduler.class);
- MergeManager mm = mock(MergeManager.class);
- Reporter r = mock(Reporter.class);
- ShuffleClientMetrics metrics = mock(ShuffleClientMetrics.class);
- ExceptionReporter except = mock(ExceptionReporter.class);
- SecretKey key = JobTokenSecretManager.createSecretKey(new byte[]{0,0,0,0});
- HttpURLConnection connection = mock(HttpURLConnection.class);
-
- Counters.Counter allErrs = mock(Counters.Counter.class);
- when(r.getCounter(anyString(), anyString()))
- .thenReturn(allErrs);
-
- Fetcher underTest = new FakeFetcher(job, id, ss, mm,
- r, metrics, except, key, connection);
-
-
- MapHost host = new MapHost("localhost", "http://localhost:8080/");
-
- ArrayList maps = new ArrayList(1);
- TaskAttemptID map1ID = TaskAttemptID.forName("attempt_0_1_m_1_1");
- maps.add(map1ID);
- TaskAttemptID map2ID = TaskAttemptID.forName("attempt_0_1_m_2_1");
- maps.add(map2ID);
- when(ss.getMapsForHost(host)).thenReturn(maps);
-
- String encHash = "vFE234EIFCiBgYs2tCXY/SjT8Kg=";
- String replyHash = SecureShuffleUtils.generateHash(encHash.getBytes(), key);
-
- when(connection.getResponseCode()).thenReturn(200);
- when(connection.getHeaderField(SecureShuffleUtils.HTTP_HEADER_REPLY_URL_HASH))
- .thenReturn(replyHash);
- ByteArrayInputStream in = new ByteArrayInputStream(
- "5 BOGUS DATA\nBOGUS DATA\nBOGUS DATA\n".getBytes());
- when(connection.getInputStream()).thenReturn(in);
-
- underTest.copyFromHost(host);
-
- verify(connection)
- .addRequestProperty(SecureShuffleUtils.HTTP_HEADER_URL_HASH,
- encHash);
-
- verify(allErrs).increment(1);
- verify(ss).copyFailed(map1ID, host, true);
- verify(ss).copyFailed(map2ID, host, true);
- }
-
-}
From decd9e1c74fa9fd631ebbd762848632b67c3e65b Mon Sep 17 00:00:00 2001
From: Aaron Myers
Date: Fri, 20 Jul 2012 20:46:25 +0000
Subject: [PATCH 04/39] Move entry for HDFS-3583 in CHANGES.txt to be under
branch-2.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1363949 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 7a2c6acf994..eb3e60ebe40 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -109,8 +109,6 @@ Trunk (unreleased changes)
HDFS-3630 Modify TestPersistBlocks to use both flush and hflush (sanjay)
- HDFS-3583. Convert remaining tests to Junit4. (Andrew Wang via atm)
-
OPTIMIZATIONS
BUG FIXES
@@ -352,6 +350,8 @@ Branch-2 ( Unreleased changes )
HDFS-1249. With fuse-dfs, chown which only has owner (or only group)
argument fails with Input/output error. (Colin Patrick McCabe via eli)
+ HDFS-3583. Convert remaining tests to Junit4. (Andrew Wang via atm)
+
OPTIMIZATIONS
HDFS-2982. Startup performance suffers when there are many edit log
From 80e2b4117be43cb744cbfe9949ce6c590140fbb3 Mon Sep 17 00:00:00 2001
From: Aaron Myers
Date: Fri, 20 Jul 2012 20:50:30 +0000
Subject: [PATCH 05/39] HADOOP-8609. IPC server logs a useless message when
shutting down socket. Contributed by Jon Zuanich.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1363950 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-common-project/hadoop-common/CHANGES.txt | 3 +++
.../src/main/java/org/apache/hadoop/ipc/Server.java | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index dc60a12815b..d4c4e6a07cb 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -268,6 +268,9 @@ Branch-2 ( Unreleased changes )
serializer or deserializer isn't available
(Madhukara Phatak via harsh)
+ HADOOP-8609. IPC server logs a useless message when shutting down socket.
+ (Jon Zuanich via atm)
+
BUG FIXES
HADOOP-8372. NetUtils.normalizeHostName() incorrectly handles hostname
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java
index d751e551bb6..7f63d81278e 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/Server.java
@@ -1643,7 +1643,7 @@ public abstract class Server {
if (!channel.isOpen())
return;
try {socket.shutdownOutput();} catch(Exception e) {
- LOG.warn("Ignoring socket shutdown exception");
+ LOG.debug("Ignoring socket shutdown exception", e);
}
if (channel.isOpen()) {
try {channel.close();} catch(Exception e) {}
From 9c9f29ac7e3e3533880e0b2593866a17f10087bd Mon Sep 17 00:00:00 2001
From: Alejandro Abdelnur
Date: Fri, 20 Jul 2012 23:19:23 +0000
Subject: [PATCH 06/39] MAPREDUCE-987. Exposing MiniDFS and MiniMR clusters as
a single process command-line. (ahmed via tucu)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1364020 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-mapreduce-project/CHANGES.txt | 3 +
.../mapreduce/MiniHadoopClusterManager.java | 316 ++++++++++++++++++
.../apache/hadoop/test/MapredTestDriver.java | 3 +
.../src/site/apt/CLIMiniCluster.apt.vm | 84 +++++
.../src/site/apt/index.apt.vm | 1 +
5 files changed, 407 insertions(+)
create mode 100644 hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/MiniHadoopClusterManager.java
create mode 100644 hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/CLIMiniCluster.apt.vm
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index 8639a6f7625..d8a430efd59 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -132,6 +132,9 @@ Branch-2 ( Unreleased changes )
NEW FEATURES
+ MAPREDUCE-987. Exposing MiniDFS and MiniMR clusters as a single process
+ command-line. (ahmed via tucu)
+
IMPROVEMENTS
MAPREDUCE-4157. ResourceManager should not kill apps that are well behaved
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/MiniHadoopClusterManager.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/MiniHadoopClusterManager.java
new file mode 100644
index 00000000000..35b5e30b4fc
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/MiniHadoopClusterManager.java
@@ -0,0 +1,316 @@
+/**
+ * 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.mapreduce;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.StartupOption;
+import org.apache.hadoop.mapred.JobConf;
+import org.apache.hadoop.mapred.MiniMRClientCluster;
+import org.apache.hadoop.mapred.MiniMRClientClusterFactory;
+import org.apache.hadoop.mapreduce.v2.jobhistory.JHAdminConfig;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.server.MiniYARNCluster;
+import org.mortbay.util.ajax.JSON;
+
+/**
+ * This class drives the creation of a mini-cluster on the local machine. By
+ * default, a MiniDFSCluster and MiniMRCluster are spawned on the first
+ * available ports that are found.
+ *
+ * A series of command line flags controls the startup cluster options.
+ *
+ * This class can dump a Hadoop configuration and some basic metadata (in JSON)
+ * into a text file.
+ *
+ * To shutdown the cluster, kill the process.
+ */
+public class MiniHadoopClusterManager {
+ private static final Log LOG = LogFactory
+ .getLog(MiniHadoopClusterManager.class);
+
+ private MiniMRClientCluster mr;
+ private MiniDFSCluster dfs;
+ private String writeDetails;
+ private int numNodeManagers;
+ private int numDataNodes;
+ private int nnPort;
+ private int rmPort;
+ private int jhsPort;
+ private StartupOption dfsOpts;
+ private boolean noDFS;
+ private boolean noMR;
+ private String fs;
+ private String writeConfig;
+ private JobConf conf;
+
+ /**
+ * Creates configuration options object.
+ */
+ @SuppressWarnings("static-access")
+ private Options makeOptions() {
+ Options options = new Options();
+ options
+ .addOption("nodfs", false, "Don't start a mini DFS cluster")
+ .addOption("nomr", false, "Don't start a mini MR cluster")
+ .addOption("nodemanagers", true,
+ "How many nodemanagers to start (default 1)")
+ .addOption("datanodes", true, "How many datanodes to start (default 1)")
+ .addOption("format", false, "Format the DFS (default false)")
+ .addOption("nnport", true, "NameNode port (default 0--we choose)")
+ .addOption(
+ "namenode",
+ true,
+ "URL of the namenode (default "
+ + "is either the DFS cluster or a temporary dir)")
+ .addOption("rmport", true,
+ "ResourceManager port (default 0--we choose)")
+ .addOption("jhsport", true,
+ "JobHistoryServer port (default 0--we choose)")
+ .addOption(
+ OptionBuilder.hasArgs().withArgName("property=value")
+ .withDescription("Options to pass into configuration object")
+ .create("D"))
+ .addOption(
+ OptionBuilder.hasArg().withArgName("path").withDescription(
+ "Save configuration to this XML file.").create("writeConfig"))
+ .addOption(
+ OptionBuilder.hasArg().withArgName("path").withDescription(
+ "Write basic information to this JSON file.").create(
+ "writeDetails"))
+ .addOption(
+ OptionBuilder.withDescription("Prints option help.").create("help"));
+ return options;
+ }
+
+ /**
+ * Main entry-point.
+ *
+ * @throws URISyntaxException
+ */
+ public void run(String[] args) throws IOException, URISyntaxException {
+ if (!parseArguments(args)) {
+ return;
+ }
+ start();
+ sleepForever();
+ }
+
+ private void sleepForever() {
+ while (true) {
+ try {
+ Thread.sleep(1000 * 60);
+ } catch (InterruptedException _) {
+ // nothing
+ }
+ }
+ }
+
+ /**
+ * Starts DFS and MR clusters, as specified in member-variable options. Also
+ * writes out configuration and details, if requested.
+ *
+ * @throws IOException
+ * @throws FileNotFoundException
+ * @throws URISyntaxException
+ */
+ public void start() throws IOException, FileNotFoundException,
+ URISyntaxException {
+ if (!noDFS) {
+ dfs = new MiniDFSCluster(nnPort, conf, numDataNodes, true, true,
+ dfsOpts, null, null);
+ LOG.info("Started MiniDFSCluster -- namenode on port "
+ + dfs.getNameNodePort());
+ }
+ if (!noMR) {
+ if (fs == null && dfs != null) {
+ fs = dfs.getFileSystem().getUri().toString();
+ } else if (fs == null) {
+ fs = "file:///tmp/minimr-" + System.nanoTime();
+ }
+ FileSystem.setDefaultUri(conf, new URI(fs));
+ // Instruct the minicluster to use fixed ports, so user will know which
+ // ports to use when communicating with the cluster.
+ conf.setBoolean(YarnConfiguration.YARN_MINICLUSTER_FIXED_PORTS, true);
+ conf.setBoolean(JHAdminConfig.MR_HISTORY_MINICLUSTER_FIXED_PORTS, true);
+ conf.set(YarnConfiguration.RM_ADDRESS, MiniYARNCluster.getHostname()
+ + ":" + this.rmPort);
+ conf.set(JHAdminConfig.MR_HISTORY_ADDRESS, MiniYARNCluster.getHostname()
+ + ":" + this.jhsPort);
+ mr = MiniMRClientClusterFactory.create(this.getClass(), numNodeManagers,
+ conf);
+ LOG.info("Started MiniMRCluster");
+ }
+
+ if (writeConfig != null) {
+ FileOutputStream fos = new FileOutputStream(new File(writeConfig));
+ conf.writeXml(fos);
+ fos.close();
+ }
+
+ if (writeDetails != null) {
+ Map map = new TreeMap();
+ if (dfs != null) {
+ map.put("namenode_port", dfs.getNameNodePort());
+ }
+ if (mr != null) {
+ map.put("resourcemanager_port", mr.getConfig().get(
+ YarnConfiguration.RM_ADDRESS).split(":")[1]);
+ }
+ FileWriter fw = new FileWriter(new File(writeDetails));
+ fw.write(new JSON().toJSON(map));
+ fw.close();
+ }
+ }
+
+ /**
+ * Shuts down in-process clusters.
+ *
+ * @throws IOException
+ */
+ public void stop() throws IOException {
+ if (mr != null) {
+ mr.stop();
+ }
+ if (dfs != null) {
+ dfs.shutdown();
+ }
+ }
+
+ /**
+ * Parses arguments and fills out the member variables.
+ *
+ * @param args
+ * Command-line arguments.
+ * @return true on successful parse; false to indicate that the program should
+ * exit.
+ */
+ private boolean parseArguments(String[] args) {
+ Options options = makeOptions();
+ CommandLine cli;
+ try {
+ CommandLineParser parser = new GnuParser();
+ cli = parser.parse(options, args);
+ } catch (ParseException e) {
+ LOG.warn("options parsing failed: " + e.getMessage());
+ new HelpFormatter().printHelp("...", options);
+ return false;
+ }
+
+ if (cli.hasOption("help")) {
+ new HelpFormatter().printHelp("...", options);
+ return false;
+ }
+ if (cli.getArgs().length > 0) {
+ for (String arg : cli.getArgs()) {
+ System.err.println("Unrecognized option: " + arg);
+ new HelpFormatter().printHelp("...", options);
+ return false;
+ }
+ }
+
+ // MR
+ noMR = cli.hasOption("nomr");
+ numNodeManagers = intArgument(cli, "nodemanagers", 1);
+ rmPort = intArgument(cli, "rmport", 0);
+ jhsPort = intArgument(cli, "jhsport", 0);
+ fs = cli.getOptionValue("namenode");
+
+ // HDFS
+ noDFS = cli.hasOption("nodfs");
+ numDataNodes = intArgument(cli, "datanodes", 1);
+ nnPort = intArgument(cli, "nnport", 0);
+ dfsOpts = cli.hasOption("format") ? StartupOption.FORMAT
+ : StartupOption.REGULAR;
+
+ // Runner
+ writeDetails = cli.getOptionValue("writeDetails");
+ writeConfig = cli.getOptionValue("writeConfig");
+
+ // General
+ conf = new JobConf();
+ updateConfiguration(conf, cli.getOptionValues("D"));
+
+ return true;
+ }
+
+ /**
+ * Updates configuration based on what's given on the command line.
+ *
+ * @param conf
+ * The configuration object
+ * @param keyvalues
+ * An array of interleaved key value pairs.
+ */
+ private void updateConfiguration(JobConf conf, String[] keyvalues) {
+ int num_confs_updated = 0;
+ if (keyvalues != null) {
+ for (String prop : keyvalues) {
+ String[] keyval = prop.split("=", 2);
+ if (keyval.length == 2) {
+ conf.set(keyval[0], keyval[1]);
+ num_confs_updated++;
+ } else {
+ LOG.warn("Ignoring -D option " + prop);
+ }
+ }
+ }
+ LOG.info("Updated " + num_confs_updated
+ + " configuration settings from command line.");
+ }
+
+ /**
+ * Extracts an integer argument with specified default value.
+ */
+ private int intArgument(CommandLine cli, String argName, int default_) {
+ String o = cli.getOptionValue(argName);
+ if (o == null) {
+ return default_;
+ } else {
+ return Integer.parseInt(o);
+ }
+ }
+
+ /**
+ * Starts a MiniHadoopCluster.
+ *
+ * @throws URISyntaxException
+ */
+ public static void main(String[] args) throws IOException, URISyntaxException {
+ new MiniHadoopClusterManager().run(args);
+ }
+}
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/test/MapredTestDriver.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/test/MapredTestDriver.java
index 4ab14adba15..7355b8e9fbb 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/test/MapredTestDriver.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/test/MapredTestDriver.java
@@ -29,6 +29,7 @@ import org.apache.hadoop.mapred.TestSequenceFileInputFormat;
import org.apache.hadoop.mapred.TestTextInputFormat;
import org.apache.hadoop.mapred.ThreadedMapBenchmark;
import org.apache.hadoop.mapreduce.FailJob;
+import org.apache.hadoop.mapreduce.MiniHadoopClusterManager;
import org.apache.hadoop.mapreduce.SleepJob;
import org.apache.hadoop.util.ProgramDriver;
@@ -101,6 +102,8 @@ public class MapredTestDriver {
"Job History Log analyzer.");
pgd.addClass(SliveTest.class.getSimpleName(), SliveTest.class,
"HDFS Stress Test and Live Data Verification.");
+ pgd.addClass("minicluster", MiniHadoopClusterManager.class,
+ "Single process HDFS and MR cluster.");
} catch(Throwable e) {
e.printStackTrace();
}
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/CLIMiniCluster.apt.vm b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/CLIMiniCluster.apt.vm
new file mode 100644
index 00000000000..957b99463f0
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/CLIMiniCluster.apt.vm
@@ -0,0 +1,84 @@
+~~ Licensed 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. See accompanying LICENSE file.
+
+ ---
+ Hadoop MapReduce Next Generation ${project.version} - CLI MiniCluster.
+ ---
+ ---
+ ${maven.build.timestamp}
+
+Hadoop MapReduce Next Generation - CLI MiniCluster.
+
+ \[ {{{./index.html}Go Back}} \]
+
+%{toc|section=1|fromDepth=0}
+
+* {Purpose}
+
+ Using the CLI MiniCluster, users can simply start and stop a single-node
+ Hadoop cluster with a single command, and without the need to set any
+ environment variables or manage configuration files. The CLI MiniCluster
+ starts both a <<>>/<<>> & <<>> clusters.
+
+ This is useful for cases where users want to quickly experiment with a real
+ Hadoop cluster or test non-Java programs that rely on significant Hadoop
+ functionality.
+
+* {Hadoop Tarball}
+
+ You should be able to obtain the Hadoop tarball from the release. Also, you
+ can directly create a tarball from the source:
+
++---+
+$ mvn clean install -DskipTests
+$ mvn package -Pdist -Dtar -DskipTests -Dmaven.javadoc.skip
++---+
+ <> You will need protoc installed of version 2.4.1 or greater.
+
+ The tarball should be available in <<>> directory.
+
+* {Running the MiniCluster}
+
+ From inside the root directory of the extracted tarball, you can start the CLI
+ MiniCluster using the following command:
+
++---+
+$ bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-${project.version}-tests.jar minicluster -rmport RM_PORT -jhsport JHS_PORT
++---+
+
+ In the example command above, <<>> and <<>> should be
+ replaced by the user's choice of these port numbers. If not specified, random
+ free ports will be used.
+
+ There are a number of command line arguments that the users can use to control
+ which services to start, and to pass other configuration properties.
+ The available command line arguments:
+
++---+
+$ -D Options to pass into configuration object
+$ -datanodes How many datanodes to start (default 1)
+$ -format Format the DFS (default false)
+$ -help Prints option help.
+$ -jhsport JobHistoryServer port (default 0--we choose)
+$ -namenode URL of the namenode (default is either the DFS
+$ cluster or a temporary dir)
+$ -nnport NameNode port (default 0--we choose)
+$ -nodemanagers How many nodemanagers to start (default 1)
+$ -nodfs Don't start a mini DFS cluster
+$ -nomr Don't start a mini MR cluster
+$ -rmport ResourceManager port (default 0--we choose)
+$ -writeConfig Save configuration to this XML file.
+$ -writeDetails Write basic information to this JSON file.
++---+
+
+ To display this full list of available arguments, the user can pass the
+ <<<-help>>> argument to the above command.
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/index.apt.vm b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/index.apt.vm
index ced6c3471df..dd086474982 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/index.apt.vm
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/index.apt.vm
@@ -49,4 +49,5 @@ MapReduce NextGen aka YARN aka MRv2
* {{{./WebApplicationProxy.html}Web Application Proxy}}
+ * {{{./CLIMiniCluster.html}CLI MiniCluster}}
From b7b8db51dec5e14aa13ff473b5fbc21d661ded6c Mon Sep 17 00:00:00 2001
From: Todd Lipcon
Date: Mon, 23 Jul 2012 16:27:59 +0000
Subject: [PATCH 07/39] HDFS-3697. Enable fadvise readahead by default.
Contributed by Todd Lipcon.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1364698 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 +
.../org/apache/hadoop/hdfs/DFSConfigKeys.java | 2 +-
.../src/main/resources/hdfs-default.xml | 74 +++++++++++++++++++
3 files changed, 77 insertions(+), 1 deletion(-)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index eb3e60ebe40..2ea4cf39716 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -363,6 +363,8 @@ Branch-2 ( Unreleased changes )
HDFS-3110. Use directRead API to reduce the number of buffer copies in
libhdfs (Henry Robinson via todd)
+ HDFS-3697. Enable fadvise readahead by default. (todd)
+
BUG FIXES
HDFS-3385. The last block of INodeFileUnderConstruction is not
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java
index ddadbdd44f7..f97c9034e4d 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSConfigKeys.java
@@ -74,7 +74,7 @@ public class DFSConfigKeys extends CommonConfigurationKeys {
public static final String DFS_DATANODE_BALANCE_BANDWIDTHPERSEC_KEY = "dfs.datanode.balance.bandwidthPerSec";
public static final long DFS_DATANODE_BALANCE_BANDWIDTHPERSEC_DEFAULT = 1024*1024;
public static final String DFS_DATANODE_READAHEAD_BYTES_KEY = "dfs.datanode.readahead.bytes";
- public static final long DFS_DATANODE_READAHEAD_BYTES_DEFAULT = 0;
+ public static final long DFS_DATANODE_READAHEAD_BYTES_DEFAULT = 4 * 1024 * 1024; // 4MB
public static final String DFS_DATANODE_DROP_CACHE_BEHIND_WRITES_KEY = "dfs.datanode.drop.cache.behind.writes";
public static final boolean DFS_DATANODE_DROP_CACHE_BEHIND_WRITES_DEFAULT = false;
public static final String DFS_DATANODE_SYNC_BEHIND_WRITES_KEY = "dfs.datanode.sync.behind.writes";
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml b/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
index 00c5e8bc446..4815ea147ff 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/resources/hdfs-default.xml
@@ -714,6 +714,80 @@
+
+ dfs.datanode.readahead.bytes
+ 4193404
+
+ While reading block files, if the Hadoop native libraries are available,
+ the datanode can use the posix_fadvise system call to explicitly
+ page data into the operating system buffer cache ahead of the current
+ reader's position. This can improve performance especially when
+ disks are highly contended.
+
+ This configuration specifies the number of bytes ahead of the current
+ read position which the datanode will attempt to read ahead. This
+ feature may be disabled by configuring this property to 0.
+
+ If the native libraries are not available, this configuration has no
+ effect.
+
+
+
+
+ dfs.datanode.drop.cache.behind.reads
+ false
+
+ In some workloads, the data read from HDFS is known to be significantly
+ large enough that it is unlikely to be useful to cache it in the
+ operating system buffer cache. In this case, the DataNode may be
+ configured to automatically purge all data from the buffer cache
+ after it is delivered to the client. This behavior is automatically
+ disabled for workloads which read only short sections of a block
+ (e.g HBase random-IO workloads).
+
+ This may improve performance for some workloads by freeing buffer
+ cache spage usage for more cacheable data.
+
+ If the Hadoop native libraries are not available, this configuration
+ has no effect.
+
+
+
+
+ dfs.datanode.drop.cache.behind.writes
+ false
+
+ In some workloads, the data written to HDFS is known to be significantly
+ large enough that it is unlikely to be useful to cache it in the
+ operating system buffer cache. In this case, the DataNode may be
+ configured to automatically purge all data from the buffer cache
+ after it is written to disk.
+
+ This may improve performance for some workloads by freeing buffer
+ cache spage usage for more cacheable data.
+
+ If the Hadoop native libraries are not available, this configuration
+ has no effect.
+
+
+
+
+ dfs.datanode.sync.behind.writes
+ false
+
+ If this configuration is enabled, the datanode will instruct the
+ operating system to enqueue all written data to the disk immediately
+ after it is written. This differs from the usual OS policy which
+ may wait for up to 30 seconds before triggering writeback.
+
+ This may improve performance for some workloads by smoothing the
+ IO profile for data written to disk.
+
+ If the Hadoop native libraries are not available, this configuration
+ has no effect.
+
+
+
dfs.client.failover.max.attempts
15
From 97ed48e0357094ac43533c9e6bd05067b15010c6 Mon Sep 17 00:00:00 2001
From: Robert Joseph Evans
Date: Mon, 23 Jul 2012 19:31:33 +0000
Subject: [PATCH 08/39] MAPREDUCE-3893. allow capacity scheduler configs
max-apps and max-am-pct per queue (tgraves via bobby)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1364764 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-mapreduce-project/CHANGES.txt | 4 +
.../CapacitySchedulerConfiguration.java | 36 ++++++++-
.../scheduler/capacity/LeafQueue.java | 37 ++++++----
.../capacity/TestApplicationLimits.java | 74 ++++++++++++++++++-
.../src/site/apt/CapacityScheduler.apt.vm | 17 +++--
5 files changed, 143 insertions(+), 25 deletions(-)
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index d8a430efd59..42b1d991c6a 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -742,6 +742,10 @@ Release 0.23.3 - UNRELEASED
MAPREDUCE-4448. Fix NM crash during app cleanup if aggregation didn't
init. (Jason Lowe via daryn)
+ MAPREDUCE-3893. allow capacity scheduler configs maximum-applications and
+ maximum-am-resource-percent configurable on a per queue basis (tgraves via
+ bobby)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/CapacitySchedulerConfiguration.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/CapacitySchedulerConfiguration.java
index 86e2dd350da..300dda5011a 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/CapacitySchedulerConfiguration.java
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/CapacitySchedulerConfiguration.java
@@ -46,13 +46,21 @@ public class CapacitySchedulerConfiguration extends Configuration {
@Private
public static final String DOT = ".";
+ @Private
+ public static final String MAXIMUM_APPLICATIONS_SUFFIX =
+ "maximum-applications";
+
@Private
public static final String MAXIMUM_SYSTEM_APPLICATIONS =
- PREFIX + "maximum-applications";
+ PREFIX + MAXIMUM_APPLICATIONS_SUFFIX;
+
+ @Private
+ public static final String MAXIMUM_AM_RESOURCE_SUFFIX =
+ "maximum-am-resource-percent";
@Private
public static final String MAXIMUM_APPLICATION_MASTERS_RESOURCE_PERCENT =
- PREFIX + "maximum-am-resource-percent";
+ PREFIX + MAXIMUM_AM_RESOURCE_SUFFIX;
@Private
public static final String QUEUES = "queues";
@@ -131,6 +139,30 @@ public class CapacitySchedulerConfiguration extends Configuration {
return getFloat(MAXIMUM_APPLICATION_MASTERS_RESOURCE_PERCENT,
DEFAULT_MAXIMUM_APPLICATIONMASTERS_RESOURCE_PERCENT);
}
+
+
+ /**
+ * Get the maximum applications per queue setting.
+ * @param queue name of the queue
+ * @return setting specified or -1 if not set
+ */
+ public int getMaximumApplicationsPerQueue(String queue) {
+ int maxApplicationsPerQueue =
+ getInt(getQueuePrefix(queue) + MAXIMUM_APPLICATIONS_SUFFIX,
+ (int)UNDEFINED);
+ return maxApplicationsPerQueue;
+ }
+
+ /**
+ * Get the maximum am resource percent per queue setting.
+ * @param queue name of the queue
+ * @return per queue setting or defaults to the global am-resource-percent
+ * setting if per queue setting not present
+ */
+ public float getMaximumApplicationMasterResourcePerQueuePercent(String queue) {
+ return getFloat(getQueuePrefix(queue) + MAXIMUM_AM_RESOURCE_SUFFIX,
+ getMaximumApplicationMasterResourcePercent());
+ }
public float getCapacity(String queue) {
float capacity = getFloat(getQueuePrefix(queue) + CAPACITY, UNDEFINED);
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/LeafQueue.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/LeafQueue.java
index ebf594b33c1..cf303cba8f3 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/LeafQueue.java
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/LeafQueue.java
@@ -85,7 +85,7 @@ public class LeafQueue implements CSQueue {
private int maxApplications;
private int maxApplicationsPerUser;
- private float maxAMResourcePercent;
+ private float maxAMResourcePerQueuePercent;
private int maxActiveApplications; // Based on absolute max capacity
private int maxActiveAppsUsingAbsCap; // Based on absolute capacity
private int maxActiveApplicationsPerUser;
@@ -156,21 +156,25 @@ public class LeafQueue implements CSQueue {
float userLimitFactor =
cs.getConfiguration().getUserLimitFactor(getQueuePath());
- int maxSystemJobs = cs.getConfiguration().getMaximumSystemApplications();
- int maxApplications = (int)(maxSystemJobs * absoluteCapacity);
- int maxApplicationsPerUser =
+ int maxApplications = cs.getConfiguration().getMaximumApplicationsPerQueue(getQueuePath());
+ if (maxApplications < 0) {
+ int maxSystemApps = cs.getConfiguration().getMaximumSystemApplications();
+ maxApplications = (int)(maxSystemApps * absoluteCapacity);
+ }
+ maxApplicationsPerUser =
(int)(maxApplications * (userLimit / 100.0f) * userLimitFactor);
- this.maxAMResourcePercent =
- cs.getConfiguration().getMaximumApplicationMasterResourcePercent();
+ this.maxAMResourcePerQueuePercent =
+ cs.getConfiguration().
+ getMaximumApplicationMasterResourcePerQueuePercent(getQueuePath());
int maxActiveApplications =
CSQueueUtils.computeMaxActiveApplications(
cs.getClusterResources(), this.minimumAllocation,
- maxAMResourcePercent, absoluteMaxCapacity);
+ maxAMResourcePerQueuePercent, absoluteMaxCapacity);
this.maxActiveAppsUsingAbsCap =
CSQueueUtils.computeMaxActiveApplications(
cs.getClusterResources(), this.minimumAllocation,
- maxAMResourcePercent, absoluteCapacity);
+ maxAMResourcePerQueuePercent, absoluteCapacity);
int maxActiveApplicationsPerUser =
CSQueueUtils.computeMaxActiveApplicationsPerUser(maxActiveAppsUsingAbsCap, userLimit,
userLimitFactor);
@@ -265,15 +269,16 @@ public class LeafQueue implements CSQueue {
"userLimitFactor = " + userLimitFactor +
" [= configuredUserLimitFactor ]" + "\n" +
"maxApplications = " + maxApplications +
- " [= (int)(configuredMaximumSystemApplications * absoluteCapacity) ]" +
+ " [= configuredMaximumSystemApplicationsPerQueue or" +
+ " (int)(configuredMaximumSystemApplications * absoluteCapacity)]" +
"\n" +
"maxApplicationsPerUser = " + maxApplicationsPerUser +
" [= (int)(maxApplications * (userLimit / 100.0f) * " +
"userLimitFactor) ]" + "\n" +
"maxActiveApplications = " + maxActiveApplications +
" [= max(" +
- "(int)ceil((clusterResourceMemory / minimumAllocation) *" +
- "maxAMResourcePercent * absoluteMaxCapacity)," +
+ "(int)ceil((clusterResourceMemory / minimumAllocation) * " +
+ "maxAMResourcePerQueuePercent * absoluteMaxCapacity)," +
"1) ]" + "\n" +
"maxActiveAppsUsingAbsCap = " + maxActiveAppsUsingAbsCap +
" [= max(" +
@@ -290,7 +295,7 @@ public class LeafQueue implements CSQueue {
"(clusterResourceMemory * absoluteCapacity)]" + "\n" +
"absoluteUsedCapacity = " + absoluteUsedCapacity +
" [= usedResourcesMemory / clusterResourceMemory]" + "\n" +
- "maxAMResourcePercent = " + maxAMResourcePercent +
+ "maxAMResourcePerQueuePercent = " + maxAMResourcePerQueuePercent +
" [= configuredMaximumAMResourcePercent ]" + "\n" +
"minimumAllocationFactor = " + minimumAllocationFactor +
" [= (float)(maximumAllocationMemory - minimumAllocationMemory) / " +
@@ -1387,11 +1392,11 @@ public class LeafQueue implements CSQueue {
maxActiveApplications =
CSQueueUtils.computeMaxActiveApplications(
clusterResource, minimumAllocation,
- maxAMResourcePercent, absoluteMaxCapacity);
+ maxAMResourcePerQueuePercent, absoluteMaxCapacity);
maxActiveAppsUsingAbsCap =
- CSQueueUtils.computeMaxActiveApplications(
- clusterResource, minimumAllocation,
- maxAMResourcePercent, absoluteCapacity);
+ CSQueueUtils.computeMaxActiveApplications(
+ clusterResource, minimumAllocation,
+ maxAMResourcePerQueuePercent, absoluteCapacity);
maxActiveApplicationsPerUser =
CSQueueUtils.computeMaxActiveApplicationsPerUser(
maxActiveAppsUsingAbsCap, userLimit, userLimitFactor);
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimits.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimits.java
index 8876bd338dd..f2f0e8d770f 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimits.java
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/scheduler/capacity/TestApplicationLimits.java
@@ -158,7 +158,9 @@ public class TestApplicationLimits {
int expectedMaxActiveApps =
Math.max(1,
(int)Math.ceil(((float)clusterResource.getMemory() / (1*GB)) *
- csConf.getMaximumApplicationMasterResourcePercent() *
+ csConf.
+ getMaximumApplicationMasterResourcePerQueuePercent(
+ queue.getQueuePath()) *
queue.getAbsoluteMaximumCapacity()));
assertEquals(expectedMaxActiveApps,
queue.getMaximumActiveApplications());
@@ -183,7 +185,9 @@ public class TestApplicationLimits {
expectedMaxActiveApps =
Math.max(1,
(int)Math.ceil(((float)clusterResource.getMemory() / (1*GB)) *
- csConf.getMaximumApplicationMasterResourcePercent() *
+ csConf.
+ getMaximumApplicationMasterResourcePerQueuePercent(
+ queue.getQueuePath()) *
queue.getAbsoluteMaximumCapacity()));
assertEquals(expectedMaxActiveApps,
queue.getMaximumActiveApplications());
@@ -200,6 +204,72 @@ public class TestApplicationLimits {
(int)(clusterResource.getMemory() * queue.getAbsoluteCapacity()),
queue.getMetrics().getAvailableMB()
);
+
+ // should return -1 if per queue setting not set
+ assertEquals((int)csConf.UNDEFINED, csConf.getMaximumApplicationsPerQueue(queue.getQueuePath()));
+ int expectedMaxApps = (int)(csConf.DEFAULT_MAXIMUM_SYSTEM_APPLICATIIONS *
+ queue.getAbsoluteCapacity());
+ assertEquals(expectedMaxApps, queue.getMaxApplications());
+
+ int expectedMaxAppsPerUser = (int)(expectedMaxApps *
+ (queue.getUserLimit()/100.0f) * queue.getUserLimitFactor());
+ assertEquals(expectedMaxAppsPerUser, queue.getMaxApplicationsPerUser());
+
+ // should default to global setting if per queue setting not set
+ assertEquals((long) csConf.DEFAULT_MAXIMUM_APPLICATIONMASTERS_RESOURCE_PERCENT,
+ (long) csConf.getMaximumApplicationMasterResourcePerQueuePercent(queue.getQueuePath()));
+
+ // Change the per-queue max AM resources percentage.
+ csConf.setFloat(
+ "yarn.scheduler.capacity." +
+ queue.getQueuePath() +
+ ".maximum-am-resource-percent",
+ 0.5f);
+ // Re-create queues to get new configs.
+ queues = new HashMap();
+ root =
+ CapacityScheduler.parseQueue(csContext, csConf, null, "root",
+ queues, queues,
+ CapacityScheduler.queueComparator,
+ CapacityScheduler.applicationComparator,
+ TestUtils.spyHook);
+ clusterResource = Resources.createResource(100 * 16 * GB);
+
+ queue = (LeafQueue)queues.get(A);
+ expectedMaxActiveApps =
+ Math.max(1,
+ (int)Math.ceil(((float)clusterResource.getMemory() / (1*GB)) *
+ csConf.
+ getMaximumApplicationMasterResourcePerQueuePercent(
+ queue.getQueuePath()) *
+ queue.getAbsoluteMaximumCapacity()));
+
+ assertEquals((long) 0.5,
+ (long) csConf.getMaximumApplicationMasterResourcePerQueuePercent(queue.getQueuePath()));
+ assertEquals(expectedMaxActiveApps,
+ queue.getMaximumActiveApplications());
+
+ // Change the per-queue max applications.
+ csConf.setInt(
+ "yarn.scheduler.capacity." +
+ queue.getQueuePath() +
+ ".maximum-applications", 9999);
+ // Re-create queues to get new configs.
+ queues = new HashMap();
+ root =
+ CapacityScheduler.parseQueue(csContext, csConf, null, "root",
+ queues, queues,
+ CapacityScheduler.queueComparator,
+ CapacityScheduler.applicationComparator,
+ TestUtils.spyHook);
+
+ queue = (LeafQueue)queues.get(A);
+ assertEquals(9999, (int)csConf.getMaximumApplicationsPerQueue(queue.getQueuePath()));
+ assertEquals(9999, queue.getMaxApplications());
+
+ expectedMaxAppsPerUser = (int)(9999 *
+ (queue.getUserLimit()/100.0f) * queue.getUserLimitFactor());
+ assertEquals(expectedMaxAppsPerUser, queue.getMaxApplicationsPerUser());
}
@Test
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/CapacityScheduler.apt.vm b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/CapacityScheduler.apt.vm
index 01a9f603761..0f57f339080 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/CapacityScheduler.apt.vm
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/CapacityScheduler.apt.vm
@@ -240,17 +240,24 @@ Hadoop MapReduce Next Generation - Capacity Scheduler
*--------------------------------------+--------------------------------------+
|| Property || Description |
*--------------------------------------+--------------------------------------+
-| <<>> | |
+| <<>> / |
+| <<.maximum-applications>>> | |
| | Maximum number of applications in the system which can be concurrently |
| | active both running and pending. Limits on each queue are directly |
| | proportional to their queue capacities and user limits. This is a
| | hard limit and any applications submitted when this limit is reached will |
-| | be rejected. Default is 10000.|
+| | be rejected. Default is 10000. This can be set for all queues with |
+| | <<>> and can also be overridden on a |
+| | per queue basis by setting <<.maximum-applications>>>. |
*--------------------------------------+--------------------------------------+
-| yarn.scheduler.capacity.maximum-am-resource-percent | |
+| <<>> / |
+| <<.maximum-am-resource-percent>>> | |
| | Maximum percent of resources in the cluster which can be used to run |
-| | application masters - controls number of concurrent running applications. |
-| | Specified as a float - ie 0.5 = 50%. Default is 10%. |
+| | application masters - controls number of concurrent active applications. Limits on each |
+| | queue are directly proportional to their queue capacities and user limits. |
+| | Specified as a float - ie 0.5 = 50%. Default is 10%. This can be set for all queues with |
+| | <<>> and can also be overridden on a |
+| | per queue basis by setting <<.maximum-am-resource-percent>>> |
*--------------------------------------+--------------------------------------+
* Queue Administration & Permissions
From 1fd21078d8bbc78a94530ed745d4abc4e2c4418b Mon Sep 17 00:00:00 2001
From: Eli Collins
Date: Tue, 24 Jul 2012 01:53:29 +0000
Subject: [PATCH 09/39] HDFS-3709. TestStartup tests still binding to the
ephemeral port. Contributed by Eli Collins
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1364865 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 ++
.../hadoop/hdfs/server/namenode/TestStartup.java | 16 +++++++---------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 2ea4cf39716..54f197bb220 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -525,6 +525,8 @@ Branch-2 ( Unreleased changes )
HDFS-3608. fuse_dfs: detect changes in UID ticket cache. (Colin Patrick
McCabe via atm)
+ HDFS-3709. TestStartup tests still binding to the ephemeral port. (eli)
+
BREAKDOWN OF HDFS-3042 SUBTASKS
HDFS-2185. HDFS portion of ZK-based FailoverController (todd)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestStartup.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestStartup.java
index 90fa4d475f3..28e22aa4be7 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestStartup.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestStartup.java
@@ -443,16 +443,15 @@ public class TestStartup {
private void testImageChecksum(boolean compress) throws Exception {
MiniDFSCluster cluster = null;
- Configuration conf = new HdfsConfiguration();
if (compress) {
- conf.setBoolean(DFSConfigKeys.DFS_IMAGE_COMPRESSION_CODEC_KEY, true);
+ config.setBoolean(DFSConfigKeys.DFS_IMAGE_COMPRESSION_CODEC_KEY, true);
}
try {
LOG.info("\n===========================================\n" +
"Starting empty cluster");
- cluster = new MiniDFSCluster.Builder(conf)
+ cluster = new MiniDFSCluster.Builder(config)
.numDataNodes(0)
.format(true)
.build();
@@ -479,7 +478,7 @@ public class TestStartup {
LOG.info("\n===========================================\n" +
"Starting same cluster after simulated crash");
try {
- cluster = new MiniDFSCluster.Builder(conf)
+ cluster = new MiniDFSCluster.Builder(config)
.numDataNodes(0)
.format(false)
.build();
@@ -507,19 +506,18 @@ public class TestStartup {
FileSystem localFileSys;
Path hostsFile;
Path excludeFile;
- Configuration conf = new HdfsConfiguration();
int HEARTBEAT_INTERVAL = 1; // heartbeat interval in seconds
// Set up the hosts/exclude files.
- localFileSys = FileSystem.getLocal(conf);
+ localFileSys = FileSystem.getLocal(config);
Path workingDir = localFileSys.getWorkingDirectory();
Path dir = new Path(workingDir, "build/test/data/work-dir/restartnn");
hostsFile = new Path(dir, "hosts");
excludeFile = new Path(dir, "exclude");
// Setup conf
- conf.set(DFSConfigKeys.DFS_HOSTS_EXCLUDE, excludeFile.toUri().getPath());
+ config.set(DFSConfigKeys.DFS_HOSTS_EXCLUDE, excludeFile.toUri().getPath());
writeConfigFile(localFileSys, excludeFile, null);
- conf.set(DFSConfigKeys.DFS_HOSTS, hostsFile.toUri().getPath());
+ config.set(DFSConfigKeys.DFS_HOSTS, hostsFile.toUri().getPath());
// write into hosts file
ArrayListlist = new ArrayList();
byte b[] = {127, 0, 0, 1};
@@ -529,7 +527,7 @@ public class TestStartup {
int numDatanodes = 1;
try {
- cluster = new MiniDFSCluster.Builder(conf)
+ cluster = new MiniDFSCluster.Builder(config)
.numDataNodes(numDatanodes).setupHostsFile(true).build();
cluster.waitActive();
From d8f39138561170d4b691a50085121123b1ebc4d9 Mon Sep 17 00:00:00 2001
From: Aaron Myers
Date: Tue, 24 Jul 2012 15:19:08 +0000
Subject: [PATCH 10/39] HDFS-3711. Manually convert remaining tests to JUnit4.
Contributed by Andrew Wang.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365119 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 +
.../content/xdocs/faultinject_framework.xml | 5 +--
.../hadoop/hdfs/TestDataTransferProtocol.java | 14 +++---
.../hadoop/hdfs/TestFileConcurrentReader.java | 33 ++++++++++----
.../apache/hadoop/hdfs/TestFileCreation.java | 3 ++
.../hadoop/hdfs/protocolPB/TestPBHelper.java | 15 +++++--
.../datanode/TestSimulatedFSDataset.java | 30 ++++++++-----
.../hdfs/server/namenode/TestCheckpoint.java | 45 ++++++++++++++++---
.../server/namenode/TestNameNodeMXBean.java | 36 ++++++++-------
.../namenode/TestParallelImageWrite.java | 4 +-
.../server/namenode/TestNNLeaseRecovery.java | 2 +-
11 files changed, 133 insertions(+), 56 deletions(-)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 54f197bb220..9c002059ba0 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -352,6 +352,8 @@ Branch-2 ( Unreleased changes )
HDFS-3583. Convert remaining tests to Junit4. (Andrew Wang via atm)
+ HDFS-3711. Manually convert remaining tests to JUnit4. (Andrew Wang via atm)
+
OPTIMIZATIONS
HDFS-2982. Startup performance suffers when there are many edit log
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/docs/src/documentation/content/xdocs/faultinject_framework.xml b/hadoop-hdfs-project/hadoop-hdfs/src/main/docs/src/documentation/content/xdocs/faultinject_framework.xml
index 9f16ff78340..1673a5e11b5 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/docs/src/documentation/content/xdocs/faultinject_framework.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/docs/src/documentation/content/xdocs/faultinject_framework.xml
@@ -332,13 +332,12 @@ package org.apache.hadoop.fs;
import org.junit.Test;
import org.junit.Before;
-import junit.framework.TestCase;
-public class DemoFiTest extends TestCase {
+public class DemoFiTest {
public static final String BLOCK_RECEIVER_FAULT="hdfs.datanode.BlockReceiver";
@Override
@Before
- public void setUp(){
+ public void setUp() {
//Setting up the test's environment as required
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDataTransferProtocol.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDataTransferProtocol.java
index aed15d819d4..f0ff407dc2b 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDataTransferProtocol.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDataTransferProtocol.java
@@ -17,6 +17,10 @@
*/
package org.apache.hadoop.hdfs;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
@@ -29,8 +33,6 @@ import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.Random;
-import junit.framework.TestCase;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
@@ -67,7 +69,7 @@ import org.mockito.Mockito;
* This tests data transfer protocol handling in the Datanode. It sends
* various forms of wrong data and verifies that Datanode handles it well.
*/
-public class TestDataTransferProtocol extends TestCase {
+public class TestDataTransferProtocol {
private static final Log LOG = LogFactory.getLog(
"org.apache.hadoop.hdfs.TestDataTransferProtocol");
@@ -205,7 +207,8 @@ public class TestDataTransferProtocol extends TestCase {
}
}
- @Test public void testOpWrite() throws IOException {
+ @Test
+ public void testOpWrite() throws IOException {
int numDataNodes = 1;
Configuration conf = new HdfsConfiguration();
MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(numDataNodes).build();
@@ -333,7 +336,8 @@ public class TestDataTransferProtocol extends TestCase {
}
}
-@Test public void testDataTransferProtocol() throws IOException {
+ @Test
+ public void testDataTransferProtocol() throws IOException {
Random random = new Random();
int oneMil = 1024*1024;
Path file = new Path("dataprotocol.dat");
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileConcurrentReader.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileConcurrentReader.java
index fae302d5c70..97659eeab3e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileConcurrentReader.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileConcurrentReader.java
@@ -16,6 +16,10 @@
* limitations under the License.
*/
package org.apache.hadoop.hdfs;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.Arrays;
@@ -37,13 +41,17 @@ import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
/**
* This class tests the cases of a concurrent reads/writes to a file;
* ie, one writer and one or more readers can see unfinsihed blocks
*/
-public class TestFileConcurrentReader extends junit.framework.TestCase {
+public class TestFileConcurrentReader {
private enum SyncType {
SYNC,
@@ -69,18 +77,16 @@ public class TestFileConcurrentReader extends junit.framework.TestCase {
private FileSystem fileSystem;
- @Override
- protected void setUp() throws IOException {
+ @Before
+ public void setUp() throws IOException {
conf = new Configuration();
init(conf);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
cluster.shutdown();
cluster = null;
-
- super.tearDown();
}
private void init(Configuration conf) throws IOException {
@@ -145,6 +151,7 @@ public class TestFileConcurrentReader extends junit.framework.TestCase {
/**
* Test that that writes to an incomplete block are available to a reader
*/
+ @Test
public void testUnfinishedBlockRead()
throws IOException {
// create a new file in the root, write data, do no close
@@ -167,6 +174,7 @@ public class TestFileConcurrentReader extends junit.framework.TestCase {
* would result in too small a buffer to do the buffer-copy needed
* for partial chunks.
*/
+ @Test
public void testUnfinishedBlockPacketBufferOverrun() throws IOException {
// check that / exists
Path path = new Path("/");
@@ -192,6 +200,7 @@ public class TestFileConcurrentReader extends junit.framework.TestCase {
// use a small block size and a large write so that DN is busy creating
// new blocks. This makes it almost 100% sure we can reproduce
// case of client getting a DN that hasn't yet created the blocks
+ @Test
public void testImmediateReadOfNewFile()
throws IOException {
final int blockSize = 64 * 1024;
@@ -268,31 +277,39 @@ public class TestFileConcurrentReader extends junit.framework.TestCase {
// for some reason, using tranferTo evokes the race condition more often
// so test separately
+ @Test
public void testUnfinishedBlockCRCErrorTransferTo() throws IOException {
runTestUnfinishedBlockCRCError(true, SyncType.SYNC, DEFAULT_WRITE_SIZE);
}
+ @Test
public void testUnfinishedBlockCRCErrorTransferToVerySmallWrite()
throws IOException {
runTestUnfinishedBlockCRCError(true, SyncType.SYNC, SMALL_WRITE_SIZE);
}
// fails due to issue w/append, disable
+ @Ignore
+ @Test
public void _testUnfinishedBlockCRCErrorTransferToAppend()
throws IOException {
runTestUnfinishedBlockCRCError(true, SyncType.APPEND, DEFAULT_WRITE_SIZE);
}
+ @Test
public void testUnfinishedBlockCRCErrorNormalTransfer() throws IOException {
runTestUnfinishedBlockCRCError(false, SyncType.SYNC, DEFAULT_WRITE_SIZE);
}
+ @Test
public void testUnfinishedBlockCRCErrorNormalTransferVerySmallWrite()
throws IOException {
runTestUnfinishedBlockCRCError(false, SyncType.SYNC, SMALL_WRITE_SIZE);
}
// fails due to issue w/append, disable
+ @Ignore
+ @Test
public void _testUnfinishedBlockCRCErrorNormalTransferAppend()
throws IOException {
runTestUnfinishedBlockCRCError(false, SyncType.APPEND, DEFAULT_WRITE_SIZE);
@@ -441,4 +458,4 @@ public class TestFileConcurrentReader extends junit.framework.TestCase {
inputStream.close();
return numRead + startPos - 1;
}
-}
\ No newline at end of file
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileCreation.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileCreation.java
index 77eb3f86576..c3909e38139 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileCreation.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileCreation.java
@@ -72,6 +72,7 @@ import org.apache.hadoop.hdfs.server.namenode.LeaseManager;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.Time;
import org.apache.log4j.Level;
+import org.junit.Ignore;
import org.junit.Test;
/**
@@ -498,6 +499,8 @@ public class TestFileCreation {
* This test is currently not triggered because more HDFS work is
* is needed to handle persistent leases.
*/
+ @Ignore
+ @Test
public void xxxtestFileCreationNamenodeRestart() throws IOException {
Configuration conf = new HdfsConfiguration();
final int MAX_IDLE_TIME = 2000; // 2s
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/protocolPB/TestPBHelper.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/protocolPB/TestPBHelper.java
index c009c451b7c..ad560869e5d 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/protocolPB/TestPBHelper.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/protocolPB/TestPBHelper.java
@@ -17,8 +17,8 @@
*/
package org.apache.hadoop.hdfs.protocolPB;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
@@ -80,6 +80,12 @@ import com.google.common.collect.Lists;
* Tests for {@link PBHelper}
*/
public class TestPBHelper {
+
+ /**
+ * Used for asserting equality on doubles.
+ */
+ private static final double DELTA = 0.000001;
+
@Test
public void testConvertNamenodeRole() {
assertEquals(NamenodeRoleProto.BACKUP,
@@ -284,11 +290,12 @@ public class TestPBHelper {
private void compare(DatanodeInfo dn1, DatanodeInfo dn2) {
assertEquals(dn1.getAdminState(), dn2.getAdminState());
assertEquals(dn1.getBlockPoolUsed(), dn2.getBlockPoolUsed());
- assertEquals(dn1.getBlockPoolUsedPercent(), dn2.getBlockPoolUsedPercent());
+ assertEquals(dn1.getBlockPoolUsedPercent(),
+ dn2.getBlockPoolUsedPercent(), DELTA);
assertEquals(dn1.getCapacity(), dn2.getCapacity());
assertEquals(dn1.getDatanodeReport(), dn2.getDatanodeReport());
assertEquals(dn1.getDfsUsed(), dn1.getDfsUsed());
- assertEquals(dn1.getDfsUsedPercent(), dn1.getDfsUsedPercent());
+ assertEquals(dn1.getDfsUsedPercent(), dn1.getDfsUsedPercent(), DELTA);
assertEquals(dn1.getIpAddr(), dn2.getIpAddr());
assertEquals(dn1.getHostName(), dn2.getHostName());
assertEquals(dn1.getInfoPort(), dn2.getInfoPort());
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestSimulatedFSDataset.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestSimulatedFSDataset.java
index a9609dff9da..1277f21fef8 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestSimulatedFSDataset.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestSimulatedFSDataset.java
@@ -17,13 +17,16 @@
*/
package org.apache.hadoop.hdfs.server.datanode;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import junit.framework.TestCase;
-
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.protocol.Block;
@@ -33,27 +36,23 @@ import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsDatasetSpi;
import org.apache.hadoop.hdfs.server.datanode.fsdataset.ReplicaOutputStreams;
import org.apache.hadoop.hdfs.server.datanode.fsdataset.impl.FsDatasetFactory;
import org.apache.hadoop.util.DataChecksum;
+import org.junit.Before;
+import org.junit.Test;
/**
* this class tests the methods of the SimulatedFSDataset.
*/
-public class TestSimulatedFSDataset extends TestCase {
+public class TestSimulatedFSDataset {
Configuration conf = null;
static final String bpid = "BP-TEST";
static final int NUMBLOCKS = 20;
static final int BLOCK_LENGTH_MULTIPLIER = 79;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
conf = new HdfsConfiguration();
SimulatedFSDataset.setFactory(conf);
}
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
long blockIdToLen(long blkid) {
return blkid*BLOCK_LENGTH_MULTIPLIER;
@@ -90,6 +89,7 @@ public class TestSimulatedFSDataset extends TestCase {
return addSomeBlocks(fsdataset, 1);
}
+ @Test
public void testFSDatasetFactory() {
final Configuration conf = new Configuration();
FsDatasetSpi.Factory> f = FsDatasetSpi.Factory.getFactory(conf);
@@ -102,6 +102,7 @@ public class TestSimulatedFSDataset extends TestCase {
assertTrue(s.isSimulated());
}
+ @Test
public void testGetMetaData() throws IOException {
final SimulatedFSDataset fsdataset = getSimulatedFSDataset();
ExtendedBlock b = new ExtendedBlock(bpid, 1, 5, 0);
@@ -123,6 +124,7 @@ public class TestSimulatedFSDataset extends TestCase {
}
+ @Test
public void testStorageUsage() throws IOException {
final SimulatedFSDataset fsdataset = getSimulatedFSDataset();
assertEquals(fsdataset.getDfsUsed(), 0);
@@ -146,6 +148,7 @@ public class TestSimulatedFSDataset extends TestCase {
assertEquals(expectedLen, lengthRead);
}
+ @Test
public void testWriteRead() throws IOException {
final SimulatedFSDataset fsdataset = getSimulatedFSDataset();
addSomeBlocks(fsdataset);
@@ -157,6 +160,7 @@ public class TestSimulatedFSDataset extends TestCase {
}
}
+ @Test
public void testGetBlockReport() throws IOException {
SimulatedFSDataset fsdataset = getSimulatedFSDataset();
BlockListAsLongs blockReport = fsdataset.getBlockReport(bpid);
@@ -170,6 +174,7 @@ public class TestSimulatedFSDataset extends TestCase {
}
}
+ @Test
public void testInjectionEmpty() throws IOException {
SimulatedFSDataset fsdataset = getSimulatedFSDataset();
BlockListAsLongs blockReport = fsdataset.getBlockReport(bpid);
@@ -198,6 +203,7 @@ public class TestSimulatedFSDataset extends TestCase {
assertEquals(sfsdataset.getCapacity()-bytesAdded, sfsdataset.getRemaining());
}
+ @Test
public void testInjectionNonEmpty() throws IOException {
SimulatedFSDataset fsdataset = getSimulatedFSDataset();
BlockListAsLongs blockReport = fsdataset.getBlockReport(bpid);
@@ -271,6 +277,7 @@ public class TestSimulatedFSDataset extends TestCase {
}
}
+ @Test
public void testInValidBlocks() throws IOException {
final SimulatedFSDataset fsdataset = getSimulatedFSDataset();
ExtendedBlock b = new ExtendedBlock(bpid, 1, 5, 0);
@@ -282,6 +289,7 @@ public class TestSimulatedFSDataset extends TestCase {
checkInvalidBlock(b);
}
+ @Test
public void testInvalidate() throws IOException {
final SimulatedFSDataset fsdataset = getSimulatedFSDataset();
int bytesAdded = addSomeBlocks(fsdataset);
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestCheckpoint.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestCheckpoint.java
index b2496f92b15..e505e73736e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestCheckpoint.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestCheckpoint.java
@@ -20,6 +20,12 @@ package org.apache.hadoop.hdfs.server.namenode;
import static org.apache.hadoop.hdfs.server.common.Util.fileAsURI;
import static org.apache.hadoop.hdfs.server.namenode.FSImageTestUtil.assertNNHasCheckpoints;
import static org.apache.hadoop.hdfs.server.namenode.FSImageTestUtil.getNameNodeCurrentDirs;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
@@ -31,8 +37,6 @@ import java.util.Collection;
import java.util.List;
import java.util.Random;
-import junit.framework.TestCase;
-
import org.apache.commons.cli.ParseException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -69,6 +73,8 @@ import org.apache.hadoop.test.GenericTestUtils.DelayAnswer;
import org.apache.hadoop.test.GenericTestUtils.LogCapturer;
import org.apache.hadoop.util.StringUtils;
import org.apache.log4j.Level;
+import org.junit.Before;
+import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
@@ -84,7 +90,7 @@ import com.google.common.primitives.Ints;
/**
* This class tests the creation and validation of a checkpoint.
*/
-public class TestCheckpoint extends TestCase {
+public class TestCheckpoint {
static {
((Log4JLogger)FSImage.LOG).getLogger().setLevel(Level.ALL);
@@ -100,7 +106,7 @@ public class TestCheckpoint extends TestCase {
private CheckpointFaultInjector faultInjector;
- @Override
+ @Before
public void setUp() throws IOException {
FileUtil.fullyDeleteContents(new File(MiniDFSCluster.getBaseDirectory()));
@@ -139,6 +145,7 @@ public class TestCheckpoint extends TestCase {
/*
* Verify that namenode does not startup if one namedir is bad.
*/
+ @Test
public void testNameDirError() throws IOException {
LOG.info("Starting testNameDirError");
Configuration conf = new HdfsConfiguration();
@@ -180,6 +187,7 @@ public class TestCheckpoint extends TestCase {
* correctly (by removing the storage directory)
* See https://issues.apache.org/jira/browse/HDFS-2011
*/
+ @Test
public void testWriteTransactionIdHandlesIOE() throws Exception {
LOG.info("Check IOException handled correctly by writeTransactionIdFile");
ArrayList fsImageDirs = new ArrayList();
@@ -214,6 +222,7 @@ public class TestCheckpoint extends TestCase {
/*
* Simulate namenode crashing after rolling edit log.
*/
+ @Test
public void testSecondaryNamenodeError1()
throws IOException {
LOG.info("Starting testSecondaryNamenodeError1");
@@ -279,6 +288,7 @@ public class TestCheckpoint extends TestCase {
/*
* Simulate a namenode crash after uploading new image
*/
+ @Test
public void testSecondaryNamenodeError2() throws IOException {
LOG.info("Starting testSecondaryNamenodeError2");
Configuration conf = new HdfsConfiguration();
@@ -340,6 +350,7 @@ public class TestCheckpoint extends TestCase {
/*
* Simulate a secondary namenode crash after rolling the edit log.
*/
+ @Test
public void testSecondaryNamenodeError3() throws IOException {
LOG.info("Starting testSecondaryNamenodeError3");
Configuration conf = new HdfsConfiguration();
@@ -412,6 +423,7 @@ public class TestCheckpoint extends TestCase {
* back to the name-node.
* Used to truncate primary fsimage file.
*/
+ @Test
public void testSecondaryFailsToReturnImage() throws IOException {
Mockito.doThrow(new IOException("If this exception is not caught by the " +
"name-node, fs image will be truncated."))
@@ -425,6 +437,7 @@ public class TestCheckpoint extends TestCase {
* before even setting the length header. This used to cause image
* truncation. Regression test for HDFS-3330.
*/
+ @Test
public void testSecondaryFailsWithErrorBeforeSettingHeaders()
throws IOException {
Mockito.doThrow(new Error("If this exception is not caught by the " +
@@ -497,6 +510,7 @@ public class TestCheckpoint extends TestCase {
* The length header in the HTTP transfer should prevent
* this from corrupting the NN.
*/
+ @Test
public void testNameNodeImageSendFailWrongSize()
throws IOException {
LOG.info("Starting testNameNodeImageSendFailWrongSize");
@@ -511,6 +525,7 @@ public class TestCheckpoint extends TestCase {
* The digest header in the HTTP transfer should prevent
* this from corrupting the NN.
*/
+ @Test
public void testNameNodeImageSendFailWrongDigest()
throws IOException {
LOG.info("Starting testNameNodeImageSendFailWrongDigest");
@@ -528,7 +543,7 @@ public class TestCheckpoint extends TestCase {
private void doSendFailTest(String exceptionSubstring)
throws IOException {
Configuration conf = new HdfsConfiguration();
- Path file1 = new Path("checkpoint-doSendFailTest-" + getName() + ".dat");
+ Path file1 = new Path("checkpoint-doSendFailTest-doSendFailTest.dat");
MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf)
.numDataNodes(numDatanodes)
.build();
@@ -574,6 +589,7 @@ public class TestCheckpoint extends TestCase {
* Test that the NN locks its storage and edits directories, and won't start up
* if the directories are already locked
**/
+ @Test
public void testNameDirLocking() throws IOException {
Configuration conf = new HdfsConfiguration();
MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf)
@@ -603,6 +619,7 @@ public class TestCheckpoint extends TestCase {
* Test that, if the edits dir is separate from the name dir, it is
* properly locked.
**/
+ @Test
public void testSeparateEditsDirLocking() throws IOException {
Configuration conf = new HdfsConfiguration();
File editsDir = new File(MiniDFSCluster.getBaseDirectory() +
@@ -638,6 +655,7 @@ public class TestCheckpoint extends TestCase {
/**
* Test that the SecondaryNameNode properly locks its storage directories.
*/
+ @Test
public void testSecondaryNameNodeLocking() throws Exception {
// Start a primary NN so that the secondary will start successfully
Configuration conf = new HdfsConfiguration();
@@ -687,6 +705,7 @@ public class TestCheckpoint extends TestCase {
* Test that, an attempt to lock a storage that is already locked by a nodename,
* logs error message that includes JVM name of the namenode that locked it.
*/
+ @Test
public void testStorageAlreadyLockedErrorMessage() throws Exception {
Configuration conf = new HdfsConfiguration();
MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf)
@@ -763,6 +782,7 @@ public class TestCheckpoint extends TestCase {
* 2. if the NN does not contain an image, importing a checkpoint
* succeeds and re-saves the image
*/
+ @Test
public void testImportCheckpoint() throws Exception {
Configuration conf = new HdfsConfiguration();
Path testPath = new Path("/testfile");
@@ -861,6 +881,7 @@ public class TestCheckpoint extends TestCase {
/**
* Tests checkpoint in HDFS.
*/
+ @Test
public void testCheckpoint() throws IOException {
Path file1 = new Path("checkpoint.dat");
Path file2 = new Path("checkpoint2.dat");
@@ -951,6 +972,7 @@ public class TestCheckpoint extends TestCase {
/**
* Tests save namespace.
*/
+ @Test
public void testSaveNamespace() throws IOException {
MiniDFSCluster cluster = null;
DistributedFileSystem fs = null;
@@ -1057,6 +1079,7 @@ public class TestCheckpoint extends TestCase {
/* Test case to test CheckpointSignature */
@SuppressWarnings("deprecation")
+ @Test
public void testCheckpointSignature() throws IOException {
MiniDFSCluster cluster = null;
@@ -1091,6 +1114,7 @@ public class TestCheckpoint extends TestCase {
* - it then fails again for the same reason
* - it then tries to checkpoint a third time
*/
+ @Test
public void testCheckpointAfterTwoFailedUploads() throws IOException {
MiniDFSCluster cluster = null;
SecondaryNameNode secondary = null;
@@ -1147,6 +1171,7 @@ public class TestCheckpoint extends TestCase {
*
* @throws IOException
*/
+ @Test
public void testMultipleSecondaryNamenodes() throws IOException {
Configuration conf = new HdfsConfiguration();
String nameserviceId1 = "ns1";
@@ -1197,6 +1222,7 @@ public class TestCheckpoint extends TestCase {
* Test that the secondary doesn't have to re-download image
* if it hasn't changed.
*/
+ @Test
public void testSecondaryImageDownload() throws IOException {
LOG.info("Starting testSecondaryImageDownload");
Configuration conf = new HdfsConfiguration();
@@ -1279,6 +1305,7 @@ public class TestCheckpoint extends TestCase {
* It verifies that this works even though the earlier-txid checkpoint gets
* uploaded after the later-txid checkpoint.
*/
+ @Test
public void testMultipleSecondaryNNsAgainstSameNN() throws Exception {
Configuration conf = new HdfsConfiguration();
@@ -1364,6 +1391,7 @@ public class TestCheckpoint extends TestCase {
* It verifies that one of the two gets an error that it's uploading a
* duplicate checkpoint, and the other one succeeds.
*/
+ @Test
public void testMultipleSecondaryNNsAgainstSameNN2() throws Exception {
Configuration conf = new HdfsConfiguration();
@@ -1457,6 +1485,7 @@ public class TestCheckpoint extends TestCase {
* is running. The secondary should shut itself down if if talks to a NN
* with the wrong namespace.
*/
+ @Test
public void testReformatNNBetweenCheckpoints() throws IOException {
MiniDFSCluster cluster = null;
SecondaryNameNode secondary = null;
@@ -1514,6 +1543,7 @@ public class TestCheckpoint extends TestCase {
* Test that the primary NN will not serve any files to a 2NN who doesn't
* share its namespace ID, and also will not accept any files from one.
*/
+ @Test
public void testNamespaceVerifiedOnFileTransfer() throws IOException {
MiniDFSCluster cluster = null;
@@ -1575,6 +1605,7 @@ public class TestCheckpoint extends TestCase {
* the non-failed storage directory receives the checkpoint.
*/
@SuppressWarnings("deprecation")
+ @Test
public void testCheckpointWithFailedStorageDir() throws Exception {
MiniDFSCluster cluster = null;
SecondaryNameNode secondary = null;
@@ -1639,6 +1670,7 @@ public class TestCheckpoint extends TestCase {
* @throws Exception
*/
@SuppressWarnings("deprecation")
+ @Test
public void testCheckpointWithSeparateDirsAfterNameFails() throws Exception {
MiniDFSCluster cluster = null;
SecondaryNameNode secondary = null;
@@ -1711,6 +1743,7 @@ public class TestCheckpoint extends TestCase {
/**
* Test that the 2NN triggers a checkpoint after the configurable interval
*/
+ @Test
public void testCheckpointTriggerOnTxnCount() throws Exception {
MiniDFSCluster cluster = null;
SecondaryNameNode secondary = null;
@@ -1764,6 +1797,7 @@ public class TestCheckpoint extends TestCase {
* logs that connect the 2NN's old checkpoint to the current txid
* get archived. Then, the 2NN tries to checkpoint again.
*/
+ @Test
public void testSecondaryHasVeryOutOfDateImage() throws IOException {
MiniDFSCluster cluster = null;
SecondaryNameNode secondary = null;
@@ -1801,6 +1835,7 @@ public class TestCheckpoint extends TestCase {
}
}
+ @Test
public void testCommandLineParsing() throws ParseException {
SecondaryNameNode.CommandLineOpts opts =
new SecondaryNameNode.CommandLineOpts();
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java
index ab013b5fbfb..cf624b75aad 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeMXBean.java
@@ -29,8 +29,6 @@ import java.util.Map;
import javax.management.MBeanServer;
import javax.management.ObjectName;
-import junit.framework.Assert;
-
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileUtil;
import org.apache.hadoop.hdfs.MiniDFSCluster;
@@ -42,6 +40,12 @@ import org.mortbay.util.ajax.JSON;
* Class for testing {@link NameNodeMXBean} implementation
*/
public class TestNameNodeMXBean {
+
+ /**
+ * Used to assert equality between doubles
+ */
+ private static final double DELTA = 0.000001;
+
@SuppressWarnings({ "unchecked", "deprecation" })
@Test
public void testNameNodeMXBeanInfo() throws Exception {
@@ -59,36 +63,36 @@ public class TestNameNodeMXBean {
"Hadoop:service=NameNode,name=NameNodeInfo");
// get attribute "ClusterId"
String clusterId = (String) mbs.getAttribute(mxbeanName, "ClusterId");
- Assert.assertEquals(fsn.getClusterId(), clusterId);
+ assertEquals(fsn.getClusterId(), clusterId);
// get attribute "BlockPoolId"
String blockpoolId = (String) mbs.getAttribute(mxbeanName,
"BlockPoolId");
- Assert.assertEquals(fsn.getBlockPoolId(), blockpoolId);
+ assertEquals(fsn.getBlockPoolId(), blockpoolId);
// get attribute "Version"
String version = (String) mbs.getAttribute(mxbeanName, "Version");
- Assert.assertEquals(fsn.getVersion(), version);
- Assert.assertTrue(version.equals(VersionInfo.getVersion()
+ assertEquals(fsn.getVersion(), version);
+ assertTrue(version.equals(VersionInfo.getVersion()
+ ", r" + VersionInfo.getRevision()));
// get attribute "Used"
Long used = (Long) mbs.getAttribute(mxbeanName, "Used");
- Assert.assertEquals(fsn.getUsed(), used.longValue());
+ assertEquals(fsn.getUsed(), used.longValue());
// get attribute "Total"
Long total = (Long) mbs.getAttribute(mxbeanName, "Total");
- Assert.assertEquals(fsn.getTotal(), total.longValue());
+ assertEquals(fsn.getTotal(), total.longValue());
// get attribute "safemode"
String safemode = (String) mbs.getAttribute(mxbeanName, "Safemode");
- Assert.assertEquals(fsn.getSafemode(), safemode);
+ assertEquals(fsn.getSafemode(), safemode);
// get attribute nondfs
Long nondfs = (Long) (mbs.getAttribute(mxbeanName, "NonDfsUsedSpace"));
- Assert.assertEquals(fsn.getNonDfsUsedSpace(), nondfs.longValue());
+ assertEquals(fsn.getNonDfsUsedSpace(), nondfs.longValue());
// get attribute percentremaining
Float percentremaining = (Float) (mbs.getAttribute(mxbeanName,
"PercentRemaining"));
- Assert.assertEquals(fsn.getPercentRemaining(), percentremaining
- .floatValue());
+ assertEquals(fsn.getPercentRemaining(), percentremaining
+ .floatValue(), DELTA);
// get attribute Totalblocks
Long totalblocks = (Long) (mbs.getAttribute(mxbeanName, "TotalBlocks"));
- Assert.assertEquals(fsn.getTotalBlocks(), totalblocks.longValue());
+ assertEquals(fsn.getTotalBlocks(), totalblocks.longValue());
// get attribute alivenodeinfo
String alivenodeinfo = (String) (mbs.getAttribute(mxbeanName,
"LiveNodes"));
@@ -103,15 +107,15 @@ public class TestNameNodeMXBean {
assertTrue(liveNode.containsKey("numBlocks"));
assertTrue(((Long)liveNode.get("numBlocks")) == 0);
}
- Assert.assertEquals(fsn.getLiveNodes(), alivenodeinfo);
+ assertEquals(fsn.getLiveNodes(), alivenodeinfo);
// get attribute deadnodeinfo
String deadnodeinfo = (String) (mbs.getAttribute(mxbeanName,
"DeadNodes"));
- Assert.assertEquals(fsn.getDeadNodes(), deadnodeinfo);
+ assertEquals(fsn.getDeadNodes(), deadnodeinfo);
// get attribute NameDirStatuses
String nameDirStatuses = (String) (mbs.getAttribute(mxbeanName,
"NameDirStatuses"));
- Assert.assertEquals(fsn.getNameDirStatuses(), nameDirStatuses);
+ assertEquals(fsn.getNameDirStatuses(), nameDirStatuses);
Map> statusMap =
(Map>) JSON.parse(nameDirStatuses);
Collection nameDirUris = cluster.getNameDirs(0);
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestParallelImageWrite.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestParallelImageWrite.java
index f9ba34e15f4..420026103e6 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestParallelImageWrite.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestParallelImageWrite.java
@@ -26,8 +26,6 @@ import java.io.File;
import java.util.Collections;
import java.util.List;
-import junit.framework.AssertionFailedError;
-
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
@@ -132,7 +130,7 @@ public class TestParallelImageWrite {
* @param fsn - the FSNamesystem being checked.
* @param numImageDirs - the configured number of StorageDirectory of type IMAGE.
* @return - the md5 hash of the most recent FSImage files, which must all be the same.
- * @throws AssertionFailedError if image files are empty or different,
+ * @throws AssertionError if image files are empty or different,
* if less than two StorageDirectory are provided, or if the
* actual number of StorageDirectory is less than configured.
*/
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/unit/org/apache/hadoop/hdfs/server/namenode/TestNNLeaseRecovery.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/unit/org/apache/hadoop/hdfs/server/namenode/TestNNLeaseRecovery.java
index ba76a7bd19e..089cf7b92b7 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/unit/org/apache/hadoop/hdfs/server/namenode/TestNNLeaseRecovery.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/unit/org/apache/hadoop/hdfs/server/namenode/TestNNLeaseRecovery.java
@@ -18,8 +18,8 @@
package org.apache.hadoop.hdfs.server.namenode;
-import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
From 3ef19e9dbd5995634aac9b4011765e1d889ea6f5 Mon Sep 17 00:00:00 2001
From: Arun Murthy
Date: Tue, 24 Jul 2012 17:33:15 +0000
Subject: [PATCH 11/39] MAPREDUCE-4438. Add a simple, generic client to run
'easy' AMs in YARN. Contributed by Bikas Saha.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365185 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-mapreduce-project/CHANGES.txt | 3 +
.../distributedshell/ApplicationMaster.java | 5 +-
.../pom.xml | 110 +++++
.../UnmanagedAMLauncher.java | 405 ++++++++++++++++++
.../TestUnmanagedAMLauncher.java | 163 +++++++
.../src/test/resources/yarn-site.xml | 21 +
.../hadoop-yarn-applications/pom.xml | 1 +
7 files changed, 707 insertions(+), 1 deletion(-)
create mode 100644 hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/pom.xml
create mode 100644 hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/main/java/org/apache/hadoop/yarn/applications/unmanagedamlauncher/UnmanagedAMLauncher.java
create mode 100644 hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/java/org/apache/hadoop/yarn/applications/unmanagedamlauncher/TestUnmanagedAMLauncher.java
create mode 100644 hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/yarn-site.xml
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index 42b1d991c6a..18863a1a4a0 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -161,6 +161,9 @@ Release 2.1.0-alpha - Unreleased
MAPREDUCE-3451. Port Fair Scheduler to MR2 (pwendell via tucu)
+ MAPREDUCE-4438. Add a simple, generic client to run 'easy' AMs in YARN.
+ (Bikas Saha via acmurthy)
+
IMPROVEMENTS
MAPREDUCE-4440. Changed SchedulerApp and SchedulerNode to be a minimal
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/main/java/org/apache/hadoop/yarn/applications/distributedshell/ApplicationMaster.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/main/java/org/apache/hadoop/yarn/applications/distributedshell/ApplicationMaster.java
index 592ab03822a..4323962e807 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/main/java/org/apache/hadoop/yarn/applications/distributedshell/ApplicationMaster.java
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/main/java/org/apache/hadoop/yarn/applications/distributedshell/ApplicationMaster.java
@@ -290,7 +290,10 @@ public class ApplicationMaster {
Map envs = System.getenv();
appAttemptID = Records.newRecord(ApplicationAttemptId.class);
- if (!envs.containsKey(ApplicationConstants.AM_CONTAINER_ID_ENV)) {
+ if (envs.containsKey(ApplicationConstants.AM_APP_ATTEMPT_ID_ENV)) {
+ appAttemptID = ConverterUtils.toApplicationAttemptId(envs
+ .get(ApplicationConstants.AM_APP_ATTEMPT_ID_ENV));
+ } else if (!envs.containsKey(ApplicationConstants.AM_CONTAINER_ID_ENV)) {
if (cliParser.hasOption("app_attempt_id")) {
String appIdStr = cliParser.getOptionValue("app_attempt_id", "");
appAttemptID = ConverterUtils.toApplicationAttemptId(appIdStr);
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/pom.xml b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/pom.xml
new file mode 100644
index 00000000000..0dd77604ba9
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/pom.xml
@@ -0,0 +1,110 @@
+
+
+
+
+ hadoop-yarn-applications
+ org.apache.hadoop
+ 3.0.0-SNAPSHOT
+
+ 4.0.0
+ org.apache.hadoop
+ hadoop-yarn-applications-unmanaged-am-launcher
+ 3.0.0-SNAPSHOT
+ hadoop-yarn-applications-unmanaged-am-launcher
+
+
+
+ ${project.parent.parent.basedir}
+
+
+
+
+ org.apache.hadoop
+ hadoop-yarn-api
+
+
+ org.apache.hadoop
+ hadoop-yarn-common
+
+
+ org.apache.hadoop
+ hadoop-yarn-server-nodemanager
+ test
+
+
+ org.apache.hadoop
+ hadoop-yarn-server-resourcemanager
+ test
+
+
+ org.apache.hadoop
+ hadoop-yarn-server-common
+ test
+
+
+ org.apache.hadoop
+ hadoop-mapreduce-client-core
+ test
+
+
+ org.apache.hadoop
+ hadoop-yarn-applications-distributedshell
+ 3.0.0-SNAPSHOT
+ test
+
+
+ org.apache.hadoop
+ hadoop-yarn-server-tests
+ test-jar
+ test
+
+
+
+
+
+
+ maven-dependency-plugin
+
+
+ build-classpath
+ generate-sources
+
+ build-classpath
+
+
+
+ target/classes/yarn-apps-am-generated-classpath
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ ${java.home}
+
+
+
+
+
+
+
+
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/main/java/org/apache/hadoop/yarn/applications/unmanagedamlauncher/UnmanagedAMLauncher.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/main/java/org/apache/hadoop/yarn/applications/unmanagedamlauncher/UnmanagedAMLauncher.java
new file mode 100644
index 00000000000..cd5f94e544e
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/main/java/org/apache/hadoop/yarn/applications/unmanagedamlauncher/UnmanagedAMLauncher.java
@@ -0,0 +1,405 @@
+/**
+ * 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.yarn.applications.unmanagedamlauncher;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.GnuParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.yarn.api.ApplicationConstants;
+import org.apache.hadoop.yarn.api.ClientRMProtocol;
+import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationReportRequest;
+import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationReportResponse;
+import org.apache.hadoop.yarn.api.protocolrecords.GetNewApplicationRequest;
+import org.apache.hadoop.yarn.api.protocolrecords.GetNewApplicationResponse;
+import org.apache.hadoop.yarn.api.protocolrecords.SubmitApplicationRequest;
+import org.apache.hadoop.yarn.api.records.ApplicationAttemptId;
+import org.apache.hadoop.yarn.api.records.ApplicationId;
+import org.apache.hadoop.yarn.api.records.ApplicationReport;
+import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext;
+import org.apache.hadoop.yarn.api.records.ContainerLaunchContext;
+import org.apache.hadoop.yarn.api.records.FinalApplicationStatus;
+import org.apache.hadoop.yarn.api.records.Priority;
+import org.apache.hadoop.yarn.api.records.YarnApplicationState;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.exceptions.YarnRemoteException;
+import org.apache.hadoop.yarn.ipc.YarnRPC;
+import org.apache.hadoop.yarn.util.Records;
+
+/**
+ * The UnmanagedLauncher is a simple client that launches and unmanaged AM. An
+ * unmanagedAM is an AM that is not launched and managed by the RM. The client
+ * creates a new application on the RM and negotiates a new attempt id. Then it
+ * waits for the RM app state to reach be YarnApplicationState.ACCEPTED after
+ * which it spawns the AM in another process and passes it the attempt id via
+ * env variable ApplicationConstants.AM_APP_ATTEMPT_ID_ENV. The AM can be in any
+ * language. The AM can register with the RM using the attempt id and proceed as
+ * normal. The client redirects app stdout and stderr to its own stdout and
+ * stderr and waits for the AM process to exit. Then it waits for the RM to
+ * report app completion.
+ */
+public class UnmanagedAMLauncher {
+ private static final Log LOG = LogFactory.getLog(UnmanagedAMLauncher.class);
+
+ private Configuration conf;
+
+ // RPC to communicate to RM
+ private YarnRPC rpc;
+
+ // Handle to talk to the Resource Manager/Applications Manager
+ private ClientRMProtocol rmClient;
+
+ // Application master specific info to register a new Application with RM/ASM
+ private String appName = "";
+ // App master priority
+ private int amPriority = 0;
+ // Queue for App master
+ private String amQueue = "";
+ // cmd to start AM
+ private String amCmd = null;
+ // set the classpath explicitly
+ private String classpath = null;
+
+ /**
+ * @param args
+ * Command line arguments
+ */
+ public static void main(String[] args) {
+ try {
+ UnmanagedAMLauncher client = new UnmanagedAMLauncher();
+ LOG.info("Initializing Client");
+ boolean doRun = client.init(args);
+ if (!doRun) {
+ System.exit(0);
+ }
+ client.run();
+ } catch (Throwable t) {
+ LOG.fatal("Error running Client", t);
+ System.exit(1);
+ }
+ }
+
+ /**
+ */
+ public UnmanagedAMLauncher(Configuration conf) throws Exception {
+ // Set up RPC
+ this.conf = conf;
+ rpc = YarnRPC.create(conf);
+ }
+
+ public UnmanagedAMLauncher() throws Exception {
+ this(new Configuration());
+ }
+
+ private void printUsage(Options opts) {
+ new HelpFormatter().printHelp("Client", opts);
+ }
+
+ public boolean init(String[] args) throws ParseException {
+
+ Options opts = new Options();
+ opts.addOption("appname", true,
+ "Application Name. Default value - UnmanagedAM");
+ opts.addOption("priority", true, "Application Priority. Default 0");
+ opts.addOption("queue", true,
+ "RM Queue in which this application is to be submitted");
+ opts.addOption("master_memory", true,
+ "Amount of memory in MB to be requested to run the application master");
+ opts.addOption("cmd", true, "command to start unmanaged AM (required)");
+ opts.addOption("classpath", true, "additional classpath");
+ opts.addOption("help", false, "Print usage");
+ CommandLine cliParser = new GnuParser().parse(opts, args);
+
+ if (args.length == 0) {
+ printUsage(opts);
+ throw new IllegalArgumentException(
+ "No args specified for client to initialize");
+ }
+
+ if (cliParser.hasOption("help")) {
+ printUsage(opts);
+ return false;
+ }
+
+ appName = cliParser.getOptionValue("appname", "UnmanagedAM");
+ amPriority = Integer.parseInt(cliParser.getOptionValue("priority", "0"));
+ amQueue = cliParser.getOptionValue("queue", "");
+ classpath = cliParser.getOptionValue("classpath", null);
+
+ amCmd = cliParser.getOptionValue("cmd");
+ if (amCmd == null) {
+ printUsage(opts);
+ throw new IllegalArgumentException(
+ "No cmd specified for application master");
+ }
+
+ return true;
+ }
+
+ private void connectToRM() throws IOException {
+ YarnConfiguration yarnConf = new YarnConfiguration(conf);
+ InetSocketAddress rmAddress = yarnConf.getSocketAddr(
+ YarnConfiguration.RM_ADDRESS, YarnConfiguration.DEFAULT_RM_ADDRESS,
+ YarnConfiguration.DEFAULT_RM_PORT);
+ LOG.info("Connecting to ResourceManager at " + rmAddress);
+ rmClient = ((ClientRMProtocol) rpc.getProxy(ClientRMProtocol.class,
+ rmAddress, conf));
+ }
+
+ private GetNewApplicationResponse getApplication() throws YarnRemoteException {
+ GetNewApplicationRequest request = Records
+ .newRecord(GetNewApplicationRequest.class);
+ GetNewApplicationResponse response = rmClient.getNewApplication(request);
+ LOG.info("Got new application id=" + response.getApplicationId());
+ return response;
+ }
+
+ public void launchAM(ApplicationAttemptId attemptId) throws IOException {
+ Map env = System.getenv();
+ ArrayList envAMList = new ArrayList();
+ boolean setClasspath = false;
+ for (Map.Entry entry : env.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if(key.equals("CLASSPATH")) {
+ setClasspath = true;
+ if(classpath != null) {
+ value = value + File.pathSeparator + classpath;
+ }
+ }
+ envAMList.add(key + "=" + value);
+ }
+
+ if(!setClasspath && classpath!=null) {
+ envAMList.add("CLASSPATH="+classpath);
+ }
+
+ envAMList.add(ApplicationConstants.AM_APP_ATTEMPT_ID_ENV + "=" + attemptId);
+
+ String[] envAM = new String[envAMList.size()];
+ Process amProc = Runtime.getRuntime().exec(amCmd, envAMList.toArray(envAM));
+
+ final BufferedReader errReader =
+ new BufferedReader(new InputStreamReader(amProc
+ .getErrorStream()));
+ final BufferedReader inReader =
+ new BufferedReader(new InputStreamReader(amProc
+ .getInputStream()));
+
+ // read error and input streams as this would free up the buffers
+ // free the error stream buffer
+ Thread errThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ String line = errReader.readLine();
+ while((line != null) && !isInterrupted()) {
+ System.err.println(line);
+ line = errReader.readLine();
+ }
+ } catch(IOException ioe) {
+ LOG.warn("Error reading the error stream", ioe);
+ }
+ }
+ };
+ Thread outThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ String line = inReader.readLine();
+ while((line != null) && !isInterrupted()) {
+ System.out.println(line);
+ line = inReader.readLine();
+ }
+ } catch(IOException ioe) {
+ LOG.warn("Error reading the out stream", ioe);
+ }
+ }
+ };
+ try {
+ errThread.start();
+ outThread.start();
+ } catch (IllegalStateException ise) { }
+
+ // wait for the process to finish and check the exit code
+ try {
+ int exitCode = amProc.waitFor();
+ LOG.info("AM process exited with value: " + exitCode);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ // make sure that the error thread exits
+ // on Windows these threads sometimes get stuck and hang the execution
+ // timeout and join later after destroying the process.
+ errThread.join();
+ outThread.join();
+ errReader.close();
+ inReader.close();
+ } catch (InterruptedException ie) {
+ LOG.info("ShellExecutor: Interrupted while reading the error/out stream",
+ ie);
+ } catch (IOException ioe) {
+ LOG.warn("Error while closing the error/out stream", ioe);
+ }
+ amProc.destroy();
+ }
+
+ public boolean run() throws IOException {
+ LOG.info("Starting Client");
+
+ // Connect to ResourceManager
+ connectToRM();
+ assert (rmClient != null);
+
+ // Get a new application id
+ GetNewApplicationResponse newApp = getApplication();
+ ApplicationId appId = newApp.getApplicationId();
+
+ // Create launch context for app master
+ LOG.info("Setting up application submission context for ASM");
+ ApplicationSubmissionContext appContext = Records
+ .newRecord(ApplicationSubmissionContext.class);
+
+ // set the application id
+ appContext.setApplicationId(appId);
+ // set the application name
+ appContext.setApplicationName(appName);
+
+ // Set the priority for the application master
+ Priority pri = Records.newRecord(Priority.class);
+ pri.setPriority(amPriority);
+ appContext.setPriority(pri);
+
+ // Set the queue to which this application is to be submitted in the RM
+ appContext.setQueue(amQueue);
+
+ // Set up the container launch context for the application master
+ ContainerLaunchContext amContainer = Records
+ .newRecord(ContainerLaunchContext.class);
+ appContext.setAMContainerSpec(amContainer);
+
+ // unmanaged AM
+ appContext.setUnmanagedAM(true);
+ LOG.info("Setting unmanaged AM");
+
+ // Create the request to send to the applications manager
+ SubmitApplicationRequest appRequest = Records
+ .newRecord(SubmitApplicationRequest.class);
+ appRequest.setApplicationSubmissionContext(appContext);
+
+ // Submit the application to the applications manager
+ LOG.info("Submitting application to ASM");
+ rmClient.submitApplication(appRequest);
+
+ // Monitor the application to wait for launch state
+ ApplicationReport appReport = monitorApplication(appId,
+ EnumSet.of(YarnApplicationState.ACCEPTED));
+ ApplicationAttemptId attemptId = appReport.getCurrentApplicationAttemptId();
+ LOG.info("Launching application with id: " + attemptId);
+
+ // launch AM
+ launchAM(attemptId);
+
+ // Monitor the application for end state
+ appReport = monitorApplication(appId, EnumSet.of(
+ YarnApplicationState.KILLED, YarnApplicationState.FAILED,
+ YarnApplicationState.FINISHED));
+ YarnApplicationState appState = appReport.getYarnApplicationState();
+ FinalApplicationStatus appStatus = appReport.getFinalApplicationStatus();
+
+ LOG.info("App ended with state: " + appReport.getYarnApplicationState()
+ + " and status: " + appStatus);
+ if (YarnApplicationState.FINISHED == appState
+ && FinalApplicationStatus.SUCCEEDED == appStatus) {
+ LOG.info("Application has completed successfully.");
+ return true;
+ } else {
+ LOG.info("Application did finished unsuccessfully." + " YarnState="
+ + appState.toString() + ", FinalStatus=" + appStatus.toString());
+ return false;
+ }
+ }
+
+ /**
+ * Monitor the submitted application for completion. Kill application if time
+ * expires.
+ *
+ * @param appId
+ * Application Id of application to be monitored
+ * @return true if application completed successfully
+ * @throws YarnRemoteException
+ */
+ private ApplicationReport monitorApplication(ApplicationId appId,
+ Set finalState) throws YarnRemoteException {
+
+ while (true) {
+
+ // Check app status every 1 second.
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ LOG.debug("Thread sleep in monitoring loop interrupted");
+ }
+
+ // Get application report for the appId we are interested in
+ GetApplicationReportRequest reportRequest = Records
+ .newRecord(GetApplicationReportRequest.class);
+ reportRequest.setApplicationId(appId);
+ GetApplicationReportResponse reportResponse = rmClient
+ .getApplicationReport(reportRequest);
+ ApplicationReport report = reportResponse.getApplicationReport();
+
+ LOG.info("Got application report from ASM for" + ", appId="
+ + appId.getId() + ", appAttemptId="
+ + report.getCurrentApplicationAttemptId() + ", clientToken="
+ + report.getClientToken() + ", appDiagnostics="
+ + report.getDiagnostics() + ", appMasterHost=" + report.getHost()
+ + ", appQueue=" + report.getQueue() + ", appMasterRpcPort="
+ + report.getRpcPort() + ", appStartTime=" + report.getStartTime()
+ + ", yarnAppState=" + report.getYarnApplicationState().toString()
+ + ", distributedFinalState="
+ + report.getFinalApplicationStatus().toString() + ", appTrackingUrl="
+ + report.getTrackingUrl() + ", appUser=" + report.getUser());
+
+ YarnApplicationState state = report.getYarnApplicationState();
+ if (finalState.contains(state)) {
+ return report;
+ }
+
+ }
+
+ }
+
+}
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/java/org/apache/hadoop/yarn/applications/unmanagedamlauncher/TestUnmanagedAMLauncher.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/java/org/apache/hadoop/yarn/applications/unmanagedamlauncher/TestUnmanagedAMLauncher.java
new file mode 100644
index 00000000000..0c417bb7cba
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/java/org/apache/hadoop/yarn/applications/unmanagedamlauncher/TestUnmanagedAMLauncher.java
@@ -0,0 +1,163 @@
+/**
+ * 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.yarn.applications.unmanagedamlauncher;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URL;
+
+import junit.framework.Assert;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.server.MiniYARNCluster;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestUnmanagedAMLauncher {
+
+ private static final Log LOG = LogFactory
+ .getLog(TestUnmanagedAMLauncher.class);
+
+ protected static MiniYARNCluster yarnCluster = null;
+ protected static Configuration conf = new Configuration();
+
+ @BeforeClass
+ public static void setup() throws InterruptedException, IOException {
+ LOG.info("Starting up YARN cluster");
+ conf.setInt(YarnConfiguration.RM_SCHEDULER_MINIMUM_ALLOCATION_MB, 128);
+ if (yarnCluster == null) {
+ yarnCluster = new MiniYARNCluster(
+ TestUnmanagedAMLauncher.class.getName(), 1, 1, 1);
+ yarnCluster.init(conf);
+ yarnCluster.start();
+ URL url = Thread.currentThread().getContextClassLoader()
+ .getResource("yarn-site.xml");
+ if (url == null) {
+ throw new RuntimeException(
+ "Could not find 'yarn-site.xml' dummy file in classpath");
+ }
+ OutputStream os = new FileOutputStream(new File(url.getPath()));
+ yarnCluster.getConfig().writeXml(os);
+ os.close();
+ }
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ LOG.info("setup thread sleep interrupted. message=" + e.getMessage());
+ }
+ }
+
+ @AfterClass
+ public static void tearDown() throws IOException {
+ if (yarnCluster != null) {
+ yarnCluster.stop();
+ yarnCluster = null;
+ }
+ }
+
+ private static String getTestRuntimeClasspath() {
+
+ InputStream classpathFileStream = null;
+ BufferedReader reader = null;
+ String envClassPath = "";
+
+ LOG.info("Trying to generate classpath for app master from current thread's classpath");
+ try {
+
+ // Create classpath from generated classpath
+ // Check maven pom.xml for generated classpath info
+ // Works if compile time env is same as runtime. Mainly tests.
+ ClassLoader thisClassLoader = Thread.currentThread()
+ .getContextClassLoader();
+ String generatedClasspathFile = "yarn-apps-am-generated-classpath";
+ classpathFileStream = thisClassLoader
+ .getResourceAsStream(generatedClasspathFile);
+ if (classpathFileStream == null) {
+ LOG.info("Could not classpath resource from class loader");
+ return envClassPath;
+ }
+ LOG.info("Readable bytes from stream=" + classpathFileStream.available());
+ reader = new BufferedReader(new InputStreamReader(classpathFileStream));
+ String cp = reader.readLine();
+ if (cp != null) {
+ envClassPath += cp.trim() + File.pathSeparator;
+ }
+ // yarn-site.xml at this location contains proper config for mini cluster
+ URL url = thisClassLoader.getResource("yarn-site.xml");
+ envClassPath += new File(url.getFile()).getParent();
+ } catch (IOException e) {
+ LOG.info("Could not find the necessary resource to generate class path for tests. Error="
+ + e.getMessage());
+ }
+
+ try {
+ if (classpathFileStream != null) {
+ classpathFileStream.close();
+ }
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (IOException e) {
+ LOG.info("Failed to close class path file stream or reader. Error="
+ + e.getMessage());
+ }
+ return envClassPath;
+ }
+
+ @Test
+ public void testDSShell() throws Exception {
+ String classpath = getTestRuntimeClasspath();
+ String javaHome = System.getenv("JAVA_HOME");
+ if (javaHome == null) {
+ LOG.fatal("JAVA_HOME not defined. Test not running.");
+ return;
+ }
+ // start dist-shell with 0 containers because container launch will fail if
+ // there are no dist cache resources.
+ String[] args = {
+ "--classpath",
+ classpath,
+ "--cmd",
+ javaHome
+ + "/bin/java -Xmx512m "
+ + "org.apache.hadoop.yarn.applications.distributedshell.ApplicationMaster "
+ + "--container_memory 128 --num_containers 0 --priority 0 --shell_command ls" };
+
+ LOG.info("Initializing Launcher");
+ UnmanagedAMLauncher launcher = new UnmanagedAMLauncher(new Configuration(
+ yarnCluster.getConfig()));
+ boolean initSuccess = launcher.init(args);
+ Assert.assertTrue(initSuccess);
+ LOG.info("Running Launcher");
+ boolean result = launcher.run();
+
+ LOG.info("Launcher run completed. Result=" + result);
+ Assert.assertTrue(result);
+
+ }
+
+}
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/yarn-site.xml b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/yarn-site.xml
new file mode 100644
index 00000000000..7b02a822d9d
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-unmanaged-am-launcher/src/test/resources/yarn-site.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/pom.xml b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/pom.xml
index 881dbcbc057..1237a0d247d 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/pom.xml
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-applications/pom.xml
@@ -30,6 +30,7 @@
hadoop-yarn-applications-distributedshell
+ hadoop-yarn-applications-unmanaged-am-launcher
From a2d560310631c85b9996f895e3a163c4f35e538e Mon Sep 17 00:00:00 2001
From: Robert Joseph Evans
Date: Tue, 24 Jul 2012 19:00:50 +0000
Subject: [PATCH 12/39] HADOOP-8606. FileSystem.get may return the wrong
filesystem (Daryn Sharp via bobby)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365224 13f79535-47bb-0310-9956-ffa450edef68
---
.../hadoop-common/CHANGES.txt | 3 +
.../java/org/apache/hadoop/fs/FileSystem.java | 4 +-
.../hadoop/fs/TestFileSystemCaching.java | 61 ++++++++++++++++++-
3 files changed, 65 insertions(+), 3 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index d4c4e6a07cb..ebd833bdb66 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -838,6 +838,9 @@ Release 0.23.3 - UNRELEASED
HADOOP-8599. Non empty response from FileSystem.getFileBlockLocations when
asking for data beyond the end of file. (Andrey Klochkov via todd)
+ HADOOP-8606. FileSystem.get may return the wrong filesystem (Daryn Sharp
+ via bobby)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
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 62b48cf7467..f65d12c711c 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
@@ -280,11 +280,11 @@ public abstract class FileSystem extends Configured implements Closeable {
String scheme = uri.getScheme();
String authority = uri.getAuthority();
- if (scheme == null) { // no scheme: use default FS
+ if (scheme == null && authority == null) { // use default FS
return get(conf);
}
- if (authority == null) { // no authority
+ if (scheme != null && authority == null) { // no authority
URI defaultUri = getDefaultUri(conf);
if (scheme.equals(defaultUri.getScheme()) // if scheme matches default
&& defaultUri.getAuthority() != null) { // & default has authority
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
index 1e176a0a12f..910139b56ec 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
@@ -34,8 +34,8 @@ import org.junit.Test;
import java.security.PrivilegedExceptionAction;
import java.util.concurrent.Semaphore;
+import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
-import static junit.framework.Assert.assertTrue;
public class TestFileSystemCaching {
@@ -49,6 +49,65 @@ public class TestFileSystemCaching {
assertSame(fs1, fs2);
}
+ static class DefaultFs extends LocalFileSystem {
+ URI uri;
+ @Override
+ public void initialize(URI uri, Configuration conf) {
+ this.uri = uri;
+ }
+ @Override
+ public URI getUri() {
+ return uri;
+ }
+ }
+
+ @Test
+ public void testDefaultFsUris() throws Exception {
+ final Configuration conf = new Configuration();
+ conf.set("fs.defaultfs.impl", DefaultFs.class.getName());
+ final URI defaultUri = URI.create("defaultfs://host");
+ FileSystem.setDefaultUri(conf, defaultUri);
+ FileSystem fs = null;
+
+ // sanity check default fs
+ final FileSystem defaultFs = FileSystem.get(conf);
+ assertEquals(defaultUri, defaultFs.getUri());
+
+ // has scheme, no auth
+ fs = FileSystem.get(URI.create("defaultfs:/"), conf);
+ assertSame(defaultFs, fs);
+ fs = FileSystem.get(URI.create("defaultfs:///"), conf);
+ assertSame(defaultFs, fs);
+
+ // has scheme, same auth
+ fs = FileSystem.get(URI.create("defaultfs://host"), conf);
+ assertSame(defaultFs, fs);
+
+ // has scheme, different auth
+ fs = FileSystem.get(URI.create("defaultfs://host2"), conf);
+ assertNotSame(defaultFs, fs);
+
+ // no scheme, no auth
+ fs = FileSystem.get(URI.create("/"), conf);
+ assertSame(defaultFs, fs);
+
+ // no scheme, same auth
+ try {
+ fs = FileSystem.get(URI.create("//host"), conf);
+ fail("got fs with auth but no scheme");
+ } catch (Exception e) {
+ assertEquals("No FileSystem for scheme: null", e.getMessage());
+ }
+
+ // no scheme, different auth
+ try {
+ fs = FileSystem.get(URI.create("//host2"), conf);
+ fail("got fs with auth but no scheme");
+ } catch (Exception e) {
+ assertEquals("No FileSystem for scheme: null", e.getMessage());
+ }
+ }
+
public static class InitializeForeverFileSystem extends LocalFileSystem {
final static Semaphore sem = new Semaphore(0);
public void initialize(URI uri, Configuration conf) throws IOException {
From cdef12644b949c711678f31d6cf3149d3b42b61b Mon Sep 17 00:00:00 2001
From: Thomas Graves
Date: Tue, 24 Jul 2012 19:27:29 +0000
Subject: [PATCH 13/39] MAPREDUCE-4467. IndexCache failures due to missing
synchronization (Kihwal Lee via tgraves)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365240 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-mapreduce-project/CHANGES.txt | 3 ++
.../org/apache/hadoop/mapred/IndexCache.java | 28 +++++++++----------
2 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index 18863a1a4a0..0839a1486f6 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -749,6 +749,9 @@ Release 0.23.3 - UNRELEASED
maximum-am-resource-percent configurable on a per queue basis (tgraves via
bobby)
+ MAPREDUCE-4467. IndexCache failures due to missing synchronization
+ (Kihwal Lee via tgraves)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/IndexCache.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/IndexCache.java
index 2dbdf119602..54add3a81ac 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/IndexCache.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/IndexCache.java
@@ -67,13 +67,13 @@ class IndexCache {
if (info == null) {
info = readIndexFileToCache(fileName, mapId, expectedIndexOwner);
} else {
- while (isUnderConstruction(info)) {
- try {
- // In case the entry is ready after the above check but
- // before the following wait, we do timed wait.
- info.wait(200);
- } catch (InterruptedException e) {
- throw new IOException("Interrupted waiting for construction", e);
+ synchronized(info) {
+ while (isUnderConstruction(info)) {
+ try {
+ info.wait();
+ } catch (InterruptedException e) {
+ throw new IOException("Interrupted waiting for construction", e);
+ }
}
}
LOG.debug("IndexCache HIT: MapId " + mapId + " found");
@@ -101,13 +101,13 @@ class IndexCache {
IndexInformation info;
IndexInformation newInd = new IndexInformation();
if ((info = cache.putIfAbsent(mapId, newInd)) != null) {
- while (isUnderConstruction(info)) {
- try {
- // In case the entry is ready after the above check but
- // before the following wait, we do timed wait.
- info.wait(200);
- } catch (InterruptedException e) {
- throw new IOException("Interrupted waiting for construction", e);
+ synchronized(info) {
+ while (isUnderConstruction(info)) {
+ try {
+ info.wait();
+ } catch (InterruptedException e) {
+ throw new IOException("Interrupted waiting for construction", e);
+ }
}
}
LOG.debug("IndexCache HIT: MapId " + mapId + " found");
From 515a332912497792cbd862134a2eb86108a904ad Mon Sep 17 00:00:00 2001
From: Alejandro Abdelnur
Date: Tue, 24 Jul 2012 22:08:27 +0000
Subject: [PATCH 14/39] MAPREDUCE-4465. Update description of
yarn.nodemanager.address property. (bowang via tucu)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365322 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-mapreduce-project/CHANGES.txt | 3 +++
.../hadoop-yarn-common/src/main/resources/yarn-default.xml | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index 0839a1486f6..8bc35a5616d 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -151,6 +151,9 @@ Branch-2 ( Unreleased changes )
MAPREDUCE-4407. Add hadoop-yarn-server-tests--tests.jar to hadoop dist
package. (ahmed via tucu)
+ MAPREDUCE-4465. Update description of yarn.nodemanager.address property.
+ (bowang via tucu)
+
Release 2.1.0-alpha - Unreleased
INCOMPATIBLE CHANGES
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
index ba570c3e5df..8aaaf38d4d7 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
@@ -241,7 +241,7 @@
- address of node manager IPC.
+ The address of the container manager in the NM.
yarn.nodemanager.address
0.0.0.0:0
From 0bfa7d79d0fc5f127fd785e8ba3bc83dab8df991 Mon Sep 17 00:00:00 2001
From: Robert Joseph Evans
Date: Wed, 25 Jul 2012 13:56:29 +0000
Subject: [PATCH 15/39] HADOOP-8551. fs -mkdir creates parent directories
without the -p option (John George via bobby)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365588 13f79535-47bb-0310-9956-ffa450edef68
---
.../hadoop-common/CHANGES.txt | 3 +
.../org/apache/hadoop/fs/shell/Mkdir.java | 8 +-
.../src/test/resources/testHDFSConf.xml | 362 ++++++++++--------
3 files changed, 209 insertions(+), 164 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index ebd833bdb66..b5b671c7c15 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -841,6 +841,9 @@ Release 0.23.3 - UNRELEASED
HADOOP-8606. FileSystem.get may return the wrong filesystem (Daryn Sharp
via bobby)
+ HADOOP-8551. fs -mkdir creates parent directories without the -p option
+ (John George via bobby)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Mkdir.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Mkdir.java
index eb0fd22562f..d92a5343c94 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Mkdir.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Mkdir.java
@@ -23,9 +23,11 @@ import java.util.LinkedList;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.shell.PathExceptions.PathExistsException;
import org.apache.hadoop.fs.shell.PathExceptions.PathIOException;
import org.apache.hadoop.fs.shell.PathExceptions.PathIsNotDirectoryException;
+import org.apache.hadoop.fs.shell.PathExceptions.PathNotFoundException;
/**
* Create the given dir
@@ -66,7 +68,11 @@ class Mkdir extends FsCommand {
@Override
protected void processNonexistentPath(PathData item) throws IOException {
- // TODO: should use createParents to control intermediate dir creation
+ // check if parent exists. this is complicated because getParent(a/b/c/) returns a/b/c, but
+ // we want a/b
+ if (!item.fs.exists(new Path(item.path.toString()).getParent()) && !createParents) {
+ throw new PathNotFoundException(item.toString());
+ }
if (!item.fs.mkdirs(item.path)) {
throw new PathIOException(item.toString());
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testHDFSConf.xml b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testHDFSConf.xml
index 1f6a6265ff3..a611c2e3c39 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testHDFSConf.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testHDFSConf.xml
@@ -129,7 +129,7 @@
ls: directory using relative path
- -fs NAMENODE -mkdir dir1
+ -fs NAMENODE -mkdir -p dir1
-fs NAMENODE -ls
@@ -150,10 +150,10 @@
ls: directory using globbing
- -fs NAMENODE -mkdir dir1
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir3
- -fs NAMENODE -mkdir dir4
+ -fs NAMENODE -mkdir -p dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir3
+ -fs NAMENODE -mkdir -p dir4
-fs NAMENODE -ls
@@ -308,7 +308,7 @@
ls: Non-URI input dir at Namenode's path
- -fs NAMENODE -mkdir NAMENODE/user/dir1
+ -fs NAMENODE -mkdir -p NAMENODE/user/dir1
-fs NAMENODE -ls hdfs:///user/
@@ -329,7 +329,7 @@
ls: dir at hdfs:// path
- -fs NAMENODE -mkdir hdfs:///user/dir1
+ -fs NAMENODE -mkdir -p hdfs:///user/dir1
-fs NAMENODE -ls hdfs:///user/
@@ -350,9 +350,9 @@
ls: Non-URI input dir at Namenode's path using globing
- -fs NAMENODE -mkdir NAMENODE/user/dir1
- -fs NAMENODE -mkdir NAMENODE/user/dir2
- -fs NAMENODE -mkdir NAMENODE/user/dir3
+ -fs NAMENODE -mkdir -p NAMENODE/user/dir1
+ -fs NAMENODE -mkdir -p NAMENODE/user/dir2
+ -fs NAMENODE -mkdir -p NAMENODE/user/dir3
-fs NAMENODE -ls hdfs:///user/
@@ -381,9 +381,9 @@
ls: dir at hdfs:// path using globing
- -fs NAMENODE -mkdir hdfs:///user/dir1
- -fs NAMENODE -mkdir hdfs:///user/dir2
- -fs NAMENODE -mkdir hdfs:///user/dir3
+ -fs NAMENODE -mkdir -p hdfs:///user/dir1
+ -fs NAMENODE -mkdir -p hdfs:///user/dir2
+ -fs NAMENODE -mkdir -p hdfs:///user/dir3
-fs NAMENODE -ls hdfs:///user/
@@ -533,13 +533,13 @@
ls: files/directories using relative path
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
- -fs NAMENODE -mkdir dir0/dir1/dir1
- -fs NAMENODE -mkdir dir0/dir1/dir2
- -fs NAMENODE -mkdir dir0/dir2
- -fs NAMENODE -mkdir dir0/dir2/dir1
- -fs NAMENODE -mkdir dir0/dir2/dir2
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
+ -fs NAMENODE -mkdir -p dir0/dir1/dir1
+ -fs NAMENODE -mkdir -p dir0/dir1/dir2
+ -fs NAMENODE -mkdir -p dir0/dir2
+ -fs NAMENODE -mkdir -p dir0/dir2/dir1
+ -fs NAMENODE -mkdir -p dir0/dir2/dir2
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -621,13 +621,13 @@
ls: files/directories using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
- -fs NAMENODE -mkdir dir0/dir1/dir1
- -fs NAMENODE -mkdir dir0/dir1/dir2
- -fs NAMENODE -mkdir dir0/dir2
- -fs NAMENODE -mkdir dir0/dir2/dir1
- -fs NAMENODE -mkdir dir0/dir2/dir2
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
+ -fs NAMENODE -mkdir -p dir0/dir1/dir1
+ -fs NAMENODE -mkdir -p dir0/dir1/dir2
+ -fs NAMENODE -mkdir -p dir0/dir2
+ -fs NAMENODE -mkdir -p dir0/dir2/dir1
+ -fs NAMENODE -mkdir -p dir0/dir2/dir2
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -1045,7 +1045,7 @@
du: directory using relative path
- -fs NAMENODE -mkdir dir0
+ -fs NAMENODE -mkdir -p dir0
-fs NAMENODE -put CLITEST_DATA/data15bytes dir0/data15bytes
-fs NAMENODE -du dir0
@@ -1352,13 +1352,13 @@
dus: directories/files using relative path
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
- -fs NAMENODE -mkdir dir0/dir1/dir1
- -fs NAMENODE -mkdir dir0/dir1/dir2
- -fs NAMENODE -mkdir dir0/dir2
- -fs NAMENODE -mkdir dir0/dir2/dir1
- -fs NAMENODE -mkdir dir0/dir2/dir2
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
+ -fs NAMENODE -mkdir -p dir0/dir1/dir1
+ -fs NAMENODE -mkdir -p dir0/dir1/dir2
+ -fs NAMENODE -mkdir -p dir0/dir2
+ -fs NAMENODE -mkdir -p dir0/dir2/dir1
+ -fs NAMENODE -mkdir -p dir0/dir2/dir2
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -put CLITEST_DATA/data15bytes dir0/dir1/data15bytes
-fs NAMENODE -put CLITEST_DATA/data30bytes dir0/dir1/data30bytes
@@ -3275,7 +3275,7 @@
rm: removing a directory (relative path)
- -fs NAMENODE mkdir dir0
+ -fs NAMENODE mkdir -p dir0
-fs NAMENODE -rm dir0
@@ -3606,7 +3606,7 @@
rm: removing a directory (relative path)
- -fs NAMENODE -mkdir dir0
+ -fs NAMENODE -mkdir -p dir0
-fs NAMENODE -rm -r dir0
@@ -3655,10 +3655,10 @@
rm: removing directories by globbing (relative path)
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir1
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir3
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir3
-fs NAMENODE -rm -r dir*
@@ -3986,7 +3986,7 @@
put: putting file into a directory(absolute path)
- -fs NAMENODE -mkdir /dir0/dir1/data
+ -fs NAMENODE -mkdir -p /dir0/dir1/data
-fs NAMENODE -put CLITEST_DATA/data15bytes /dir0/dir1/data -->
-fs NAMENODE -du /dir0/dir1/data
@@ -4004,7 +4004,7 @@
put: putting file into a directory(relative path)
- -fs NAMENODE -mkdir dir0/dir1/data
+ -fs NAMENODE -mkdir -p dir0/dir1/data
-fs NAMENODE -put CLITEST_DATA/data15bytes dir0/dir1/data
-fs NAMENODE -du dir0/dir1/data
@@ -4044,7 +4044,7 @@
put: putting many files into an existing directory(relative path)
- -fs NAMENODE -mkdir dir0
+ -fs NAMENODE -mkdir -p dir0
-fs NAMENODE -put CLITEST_DATA/data15bytes CLITEST_DATA/data30bytes dir0
-fs NAMENODE -du dir0
@@ -4206,7 +4206,7 @@
put: putting file into a directory in hdfs:// path
- -fs NAMENODE -mkdir /dir1/data
+ -fs NAMENODE -mkdir -p /dir1/data
-fs NAMENODE -put CLITEST_DATA/data15bytes hdfs:///dir1/data
-fs NAMENODE -du hdfs:///dir1/data/
@@ -4329,7 +4329,7 @@
put: putting file into a directory in Namenode's path
- -fs NAMENODE -mkdir /dir1/data
+ -fs NAMENODE -mkdir -p /dir1/data
-fs NAMENODE -put CLITEST_DATA/data15bytes NAMENODE/dir1/data
-fs NAMENODE -du NAMENODE/dir1/data
@@ -4471,7 +4471,7 @@
copyFromLocal: copying file into a directory(absolute path)
- -fs NAMENODE -mkdir /dir0/dir1/data
+ -fs NAMENODE -mkdir -p /dir0/dir1/data
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes /dir0/dir1/data
-fs NAMENODE -du /dir0/dir1/data/*
@@ -4489,7 +4489,7 @@
copyFromLocal: copying file into a directory(relative path)
- -fs NAMENODE -mkdir dir0/dir1/data
+ -fs NAMENODE -mkdir -p dir0/dir1/data
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes dir0/dir1/data
-fs NAMENODE -du dir0/dir1/data/*
@@ -4529,7 +4529,7 @@
copyFromLocal: copying many files into an existing directory(relative path)
- -fs NAMENODE -mkdir dir0
+ -fs NAMENODE -mkdir -p dir0
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes CLITEST_DATA/data30bytes dir0
-fs NAMENODE -du dir0
@@ -5051,7 +5051,7 @@
cat: contents of files(relative path) using globbing
- -fs NAMENODE -mkdir dir0
+ -fs NAMENODE -mkdir -p dir0
-fs NAMENODE -put CLITEST_DATA/data15bytes dir0/data15bytes
-fs NAMENODE -put CLITEST_DATA/data30bytes dir0/data30bytes
-fs NAMENODE -put CLITEST_DATA/data60bytes dir0/data60bytes
@@ -5093,7 +5093,7 @@
cat: contents of files(relative path) without globbing
- -fs NAMENODE -mkdir dir0
+ -fs NAMENODE -mkdir -p dir0
-fs NAMENODE -put CLITEST_DATA/data15bytes dir0/data15bytes
-fs NAMENODE -put CLITEST_DATA/data30bytes dir0/data30bytes
-fs NAMENODE -put CLITEST_DATA/data60bytes dir0/data60bytes
@@ -5162,7 +5162,7 @@
cat: contents of directory(relative path)
- -fs NAMENODE -mkdir dir1
+ -fs NAMENODE -mkdir -p dir1
-fs NAMENODE -cat dir1
@@ -5444,11 +5444,30 @@
+
+
+ mkdir: creating directory (absolute path)
+
+ -fs NAMENODE -mkdir /dir0
+ -fs NAMENODE -mkdir /dir0/b/
+ -fs NAMENODE -du -s /dir0/b
+
+
+ -fs NAMENODE -rm /user
+
+
+
+ RegexpComparator
+ ^0\s+/dir0/b
+
+
+
+
mkdir: creating directory (relative path)
- -fs NAMENODE -mkdir dir0
+ -fs NAMENODE -mkdir -p dir0
-fs NAMENODE -du -s dir0
@@ -5497,10 +5516,10 @@
mkdir: creating many directories (relative path)
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir1
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir3
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir3
-fs NAMENODE -du -s dir*
@@ -5547,7 +5566,7 @@
mkdir: creating a directory with the name of an already existing file
-fs NAMENODE -put CLITEST_DATA/data15bytes data15bytes
- -fs NAMENODE -mkdir data15bytes
+ -fs NAMENODE -mkdir -p data15bytes
-fs NAMENODE -rm -r data15bytes
@@ -5721,10 +5740,27 @@
- mkdir: Test recreate of existing directory fails
+ mkdir: Test create of directory with no parent and no -p fails
-fs NAMENODE -rm -r -f dir0
-fs NAMENODE -mkdir dir0/dir1
+
+
+ -fs NAMENODE -rm -r dir0
+
+
+
+ RegexpComparator
+ mkdir: `dir0/dir1': No such file or directory
+
+
+
+
+
+ mkdir: Test recreate of existing directory fails
+
+ -fs NAMENODE -rm -r -f dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -mkdir dir0/dir1
@@ -5742,7 +5778,7 @@
mkdir: Test recreate of existing directory with -p succeeds
-fs NAMENODE -rm -r -f dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -mkdir -p dir0/dir1
@@ -6339,7 +6375,7 @@
stat: statistics about directory(relative path)
- -fs NAMENODE -mkdir dirtest
+ -fs NAMENODE -mkdir -p dirtest
-fs NAMENODE -stat "%n-%b-%o" dirtest
@@ -6399,7 +6435,7 @@
-fs NAMENODE -put CLITEST_DATA/data30bytes data30bytes
-fs NAMENODE -put CLITEST_DATA/data60bytes data60bytes
-fs NAMENODE -put CLITEST_DATA/data120bytes data120bytes
- -fs NAMENODE -mkdir datadir
+ -fs NAMENODE -mkdir -p datadir
-fs NAMENODE -stat "%n-%b" data*
@@ -6501,7 +6537,7 @@
-fs NAMENODE -put CLITEST_DATA/data30bytes hdfs:///dir0/data30bytes
-fs NAMENODE -put CLITEST_DATA/data60bytes hdfs:///dir0/data60bytes
-fs NAMENODE -put CLITEST_DATA/data120bytes hdfs:///dir0/data120bytes
- -fs NAMENODE -mkdir hdfs:///dir0/datadir
+ -fs NAMENODE -mkdir -p hdfs:///dir0/datadir
-fs NAMENODE -stat "%n-%b" hdfs:///dir0/data*
@@ -6588,7 +6624,7 @@
-fs NAMENODE -put CLITEST_DATA/data30bytes NAMENODE/dir0/data30bytes
-fs NAMENODE -put CLITEST_DATA/data60bytes NAMENODE/dir0/data60bytes
-fs NAMENODE -put CLITEST_DATA/data120bytes NAMENODE/dir0/data120bytes
- -fs NAMENODE -mkdir NAMENODE/dir0/datadir
+ -fs NAMENODE -mkdir -p NAMENODE/dir0/datadir
-fs NAMENODE -stat "%n-%b" NAMENODE/dir0/data*
@@ -6759,7 +6795,7 @@
tail: contents of directory(relative path)
- -fs NAMENODE -mkdir dir1
+ -fs NAMENODE -mkdir -p dir1
-fs NAMENODE -tail dir1
@@ -6966,7 +7002,7 @@
count: directory using relative path
- -fs NAMENODE -mkdir dir1
+ -fs NAMENODE -mkdir -p dir1
-fs NAMENODE -count dir1
@@ -7173,10 +7209,10 @@
count: relative path to multiple directories using globbing
- -fs NAMENODE -mkdir dir1
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir3
- -fs NAMENODE -mkdir dir4
+ -fs NAMENODE -mkdir -p dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir3
+ -fs NAMENODE -mkdir -p dir4
-fs NAMENODE -count dir*
@@ -7237,10 +7273,10 @@
count: relative path to multiple directories without globbing
- -fs NAMENODE -mkdir dir1
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir3
- -fs NAMENODE -mkdir dir4
+ -fs NAMENODE -mkdir -p dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir3
+ -fs NAMENODE -mkdir -p dir4
-fs NAMENODE -count dir1 dir2 dir3 dir4
@@ -7322,7 +7358,7 @@
count: directory using relative path with -q option
- -fs NAMENODE -mkdir dir1
+ -fs NAMENODE -mkdir -p dir1
-fs NAMENODE -setQuota 10 dir1
-fs NAMENODE -setSpaceQuota 1m dir1
-fs NAMENODE -count -q dir1
@@ -7539,10 +7575,10 @@
count: relative path to multiple directories using globbing with -q option
- -fs NAMENODE -mkdir dir1
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir3
- -fs NAMENODE -mkdir dir4
+ -fs NAMENODE -mkdir -p dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir3
+ -fs NAMENODE -mkdir -p dir4
-fs NAMENODE -setQuota 10 dir1
-fs NAMENODE -setSpaceQuota 1m dir1
-fs NAMENODE -setQuota 10 dir2
@@ -7619,10 +7655,10 @@
count: relative path to multiple directories without globbing with -q option
- -fs NAMENODE -mkdir dir1
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir3
- -fs NAMENODE -mkdir dir4
+ -fs NAMENODE -mkdir -p dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir3
+ -fs NAMENODE -mkdir -p dir4
-fs NAMENODE -setQuota 10 dir1
-fs NAMENODE -setSpaceQuota 1m dir1
-fs NAMENODE -setQuota 10 dir2
@@ -8482,8 +8518,8 @@
chmod: change permission(octal mode) of directory in relative path
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -8594,8 +8630,8 @@
chmod: change permission(normal mode) of directory in relative path
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -8662,8 +8698,8 @@
chmod: change permission(octal mode) of directory in relative path recursively
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -8730,8 +8766,8 @@
chmod: change permission(normal mode) of directory in relative path recursively
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -8949,13 +8985,13 @@
chmod: change permission(octal mode) of multiple directories in relative path using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -9125,13 +9161,13 @@
chmod: change permission(octal mode) of multiple directories in relative path without globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -9301,13 +9337,13 @@
chmod: change permission(normal mode) of multiple directories in relative path using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -9477,13 +9513,13 @@
chmod: change permission(normal mode) of multiple directories in relative path without globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -9587,13 +9623,13 @@
chmod: change permission(octal mode) of multiple directories in relative path recursively using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -9697,13 +9733,13 @@
chmod: change permission(octal mode) of multiple directories in relative path recursively without globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -9807,13 +9843,13 @@
chmod: change permission(normal mode) of multiple directories in relative path recursively using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -9917,13 +9953,13 @@
chmod: change permission(normal mode) of multiple directories in relative path recursively without globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -12183,8 +12219,8 @@
chown: change ownership of directory in relative path
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -12251,8 +12287,8 @@
chown: change ownership of directory in relative path recursively
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -12438,13 +12474,13 @@
chown: change ownership of multiple directories in relative path using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -12614,13 +12650,13 @@
chown: change ownership of multiple directories in relative path without globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -12724,13 +12760,13 @@
chown: change ownership of multiple directories recursively in relative path using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -12834,13 +12870,13 @@
chown: change ownership of multiple directories recursively in relative path without globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -13462,8 +13498,8 @@
chgrp: change group of directory in relative path
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -13530,8 +13566,8 @@
chgrp: change group of directory in relative path recursively
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
@@ -13717,13 +13753,13 @@
chgrp: change group of multiple directories in relative path using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -13893,13 +13929,13 @@
chgrp: change group of multiple directories in relative path without globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -14003,13 +14039,13 @@
chgrp: change group of multiple directories recursively in relative path using globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
@@ -14113,13 +14149,13 @@
chgrp: change group of multiple directories recursively in relative path without globbing
- -fs NAMENODE -mkdir dir0
- -fs NAMENODE -mkdir dir0/dir1
+ -fs NAMENODE -mkdir -p dir0
+ -fs NAMENODE -mkdir -p dir0/dir1
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/dir1/file1
-fs NAMENODE -touchz dir0/dir1/file2
- -fs NAMENODE -mkdir dir2
- -fs NAMENODE -mkdir dir2/dir1
+ -fs NAMENODE -mkdir -p dir2
+ -fs NAMENODE -mkdir -p dir2/dir1
-fs NAMENODE -touchz dir2/file0
-fs NAMENODE -touchz dir2/dir1/file1
-fs NAMENODE -touchz dir2/dir1/file2
From 56b0912b6cbbeff93dffbc92357b013c741fd850 Mon Sep 17 00:00:00 2001
From: Aaron Myers
Date: Wed, 25 Jul 2012 21:21:14 +0000
Subject: [PATCH 16/39] HDFS-3720. hdfs.h must get packaged. Contributed by
Colin Patrick McCabe.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365759 13f79535-47bb-0310-9956-ffa450edef68
---
.../src/main/resources/assemblies/hadoop-dist.xml | 4 ++--
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/hadoop-assemblies/src/main/resources/assemblies/hadoop-dist.xml b/hadoop-assemblies/src/main/resources/assemblies/hadoop-dist.xml
index 4762861e639..8db090062eb 100644
--- a/hadoop-assemblies/src/main/resources/assemblies/hadoop-dist.xml
+++ b/hadoop-assemblies/src/main/resources/assemblies/hadoop-dist.xml
@@ -111,9 +111,9 @@
/share/doc/hadoop/${hadoop.component}
- ${basedir}/src/main/native
+ ${basedir}/src/main/native/libhdfs
- *.h
+ hdfs.h
/include
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 9c002059ba0..2b1fb71d154 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -529,6 +529,8 @@ Branch-2 ( Unreleased changes )
HDFS-3709. TestStartup tests still binding to the ephemeral port. (eli)
+ HDFS-3720. hdfs.h must get packaged. (Colin Patrick McCabe via atm)
+
BREAKDOWN OF HDFS-3042 SUBTASKS
HDFS-2185. HDFS portion of ZK-based FailoverController (todd)
From 972953bd778081b9e8a0c3778d6df5c5e97368fa Mon Sep 17 00:00:00 2001
From: Todd Lipcon
Date: Wed, 25 Jul 2012 21:52:38 +0000
Subject: [PATCH 17/39] HDFS-3626. Creating file with invalid path can corrupt
edit log. Contributed by Todd Lipcon.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365801 13f79535-47bb-0310-9956-ffa450edef68
---
.../java/org/apache/hadoop/fs/FileUtil.java | 2 +-
.../main/java/org/apache/hadoop/fs/Path.java | 2 +-
.../java/org/apache/hadoop/fs/TestPath.java | 4 +-
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 +
.../java/org/apache/hadoop/hdfs/DFSUtil.java | 16 ++-
.../hdfs/server/namenode/FSDirectory.java | 11 +-
.../org/apache/hadoop/hdfs/TestDFSMkdirs.java | 61 +++++++---
.../org/apache/hadoop/hdfs/TestDFSUtil.java | 8 ++
.../apache/hadoop/hdfs/TestFileCreation.java | 104 ++++++++++++++++++
9 files changed, 185 insertions(+), 25 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java
index ba9bb4eafee..fd5166c69a6 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java
@@ -611,7 +611,7 @@ public class FileUtil {
*/
public static int symLink(String target, String linkname) throws IOException{
String cmd = "ln -s " + target + " " + linkname;
- Process p = Runtime.getRuntime().exec(cmd, null);
+ Process p = Runtime.getRuntime().exec(new String[]{"ln","-s",target,linkname}, null);
int returnVal = -1;
try{
returnVal = p.waitFor();
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/Path.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/Path.java
index 3d193dfad28..74c85af48bb 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/Path.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/Path.java
@@ -139,7 +139,7 @@ public class Path implements Comparable {
* Construct a path from a URI
*/
public Path(URI aUri) {
- uri = aUri;
+ uri = aUri.normalize();
}
/** Construct a Path from components. */
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestPath.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestPath.java
index 2be0f9d26b6..5032caab911 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestPath.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestPath.java
@@ -61,7 +61,7 @@ public class TestPath extends TestCase {
assertEquals(pathString, new Path(pathString).toString());
}
- public void testNormalize() {
+ public void testNormalize() throws URISyntaxException {
assertEquals("", new Path(".").toString());
assertEquals("..", new Path("..").toString());
assertEquals("/", new Path("/").toString());
@@ -75,6 +75,8 @@ public class TestPath extends TestCase {
assertEquals("foo", new Path("foo/").toString());
assertEquals("foo", new Path("foo//").toString());
assertEquals("foo/bar", new Path("foo//bar").toString());
+ assertEquals("hdfs://foo/foo2/bar/baz/",
+ new Path(new URI("hdfs://foo//foo2///bar/baz///")).toString());
if (Path.WINDOWS) {
assertEquals("c:/a/b", new Path("c:\\a\\b").toString());
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 2b1fb71d154..e955ea34ecc 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -531,6 +531,8 @@ Branch-2 ( Unreleased changes )
HDFS-3720. hdfs.h must get packaged. (Colin Patrick McCabe via atm)
+ HDFS-3626. Creating file with invalid path can corrupt edit log (todd)
+
BREAKDOWN OF HDFS-3042 SUBTASKS
HDFS-2185. HDFS portion of ZK-based FailoverController (todd)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSUtil.java
index a6adc81532b..0246620e905 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSUtil.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSUtil.java
@@ -56,6 +56,7 @@ import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.net.NodeBase;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -118,7 +119,7 @@ public class DFSUtil {
/**
* Whether the pathname is valid. Currently prohibits relative paths,
- * and names which contain a ":" or "/"
+ * names which contain a ":" or "//", or other non-canonical paths.
*/
public static boolean isValidName(String src) {
// Path must be absolute.
@@ -127,15 +128,22 @@ public class DFSUtil {
}
// Check for ".." "." ":" "/"
- StringTokenizer tokens = new StringTokenizer(src, Path.SEPARATOR);
- while(tokens.hasMoreTokens()) {
- String element = tokens.nextToken();
+ String[] components = StringUtils.split(src, '/');
+ for (int i = 0; i < components.length; i++) {
+ String element = components[i];
if (element.equals("..") ||
element.equals(".") ||
(element.indexOf(":") >= 0) ||
(element.indexOf("/") >= 0)) {
return false;
}
+
+ // The string may start or end with a /, but not have
+ // "//" in the middle.
+ if (element.isEmpty() && i != components.length - 1 &&
+ i != 0) {
+ return false;
+ }
}
return true;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java
index d7aabedc56a..fb9f54d21b5 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java
@@ -230,8 +230,15 @@ public class FSDirectory implements Closeable {
// Always do an implicit mkdirs for parent directory tree.
long modTime = now();
- if (!mkdirs(new Path(path).getParent().toString(), permissions, true,
- modTime)) {
+
+ Path parent = new Path(path).getParent();
+ if (parent == null) {
+ // Trying to add "/" as a file - this path has no
+ // parent -- avoids an NPE below.
+ return null;
+ }
+
+ if (!mkdirs(parent.toString(), permissions, true, modTime)) {
return null;
}
INodeFileUnderConstruction newNode = new INodeFileUnderConstruction(
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSMkdirs.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSMkdirs.java
index 71f0c130bf1..bea29f9c67d 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSMkdirs.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSMkdirs.java
@@ -17,8 +17,7 @@
*/
package org.apache.hadoop.hdfs;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
@@ -26,33 +25,38 @@ import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.InvalidPathException;
import org.apache.hadoop.fs.ParentNotDirectoryException;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocol;
+import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocols;
+import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.Time;
import org.junit.Test;
-
/**
- * This class tests that the DFS command mkdirs cannot create subdirectories
- * from a file when passed an illegal path. HADOOP-281.
+ * This class tests that the DFS command mkdirs only creates valid
+ * directories, and generally behaves as expected.
*/
public class TestDFSMkdirs {
+ private Configuration conf = new HdfsConfiguration();
+
+ private static final String[] NON_CANONICAL_PATHS = new String[] {
+ "//test1",
+ "/test2/..",
+ "/test2//bar",
+ "/test2/../test4",
+ "/test5/."
+ };
- private void writeFile(FileSystem fileSys, Path name) throws IOException {
- DataOutputStream stm = fileSys.create(name);
- stm.writeBytes("wchien");
- stm.close();
- }
-
/**
* Tests mkdirs can create a directory that does not exist and will
- * not create a subdirectory off a file.
+ * not create a subdirectory off a file. Regression test for HADOOP-281.
*/
@Test
public void testDFSMkdirs() throws IOException {
- Configuration conf = new HdfsConfiguration();
- MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(2).build();
+ MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).build();
FileSystem fileSys = cluster.getFileSystem();
try {
// First create a new directory with mkdirs
@@ -63,7 +67,7 @@ public class TestDFSMkdirs {
// Second, create a file in that directory.
Path myFile = new Path("/test/mkdirs/myFile");
- writeFile(fileSys, myFile);
+ DFSTestUtil.writeFile(fileSys, myFile, "hello world");
// Third, use mkdir to create a subdirectory off of that file,
// and check that it fails.
@@ -99,7 +103,7 @@ public class TestDFSMkdirs {
// Create a dir when parent dir exists as a file, should fail
IOException expectedException = null;
String filePath = "/mkdir-file-" + Time.now();
- writeFile(dfs, new Path(filePath));
+ DFSTestUtil.writeFile(dfs, new Path(filePath), "hello world");
try {
dfs.mkdir(new Path(filePath + "/mkdir"), FsPermission.getDefault());
} catch (IOException e) {
@@ -126,4 +130,29 @@ public class TestDFSMkdirs {
cluster.shutdown();
}
}
+
+ /**
+ * Regression test for HDFS-3626. Creates a file using a non-canonical path
+ * (i.e. with extra slashes between components) and makes sure that the NN
+ * rejects it.
+ */
+ @Test
+ public void testMkdirRpcNonCanonicalPath() throws IOException {
+ MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(0).build();
+ try {
+ NamenodeProtocols nnrpc = cluster.getNameNodeRpc();
+
+ for (String pathStr : NON_CANONICAL_PATHS) {
+ try {
+ nnrpc.mkdirs(pathStr, new FsPermission((short)0755), true);
+ fail("Did not fail when called with a non-canonicalized path: "
+ + pathStr);
+ } catch (InvalidPathException ipe) {
+ // expected
+ }
+ }
+ } finally {
+ cluster.shutdown();
+ }
+ }
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSUtil.java
index af1f6d6ea57..06b4ed0cb8a 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSUtil.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSUtil.java
@@ -620,4 +620,12 @@ public class TestDFSUtil {
assertEquals(1, uris.size());
assertTrue(uris.contains(new URI("hdfs://" + NN1_SRVC_ADDR)));
}
+
+ @Test
+ public void testIsValidName() {
+ assertFalse(DFSUtil.isValidName("/foo/../bar"));
+ assertFalse(DFSUtil.isValidName("/foo//bar"));
+ assertTrue(DFSUtil.isValidName("/"));
+ assertTrue(DFSUtil.isValidName("/bar/"));
+ }
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileCreation.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileCreation.java
index c3909e38139..cb79f67c905 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileCreation.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestFileCreation.java
@@ -41,6 +41,8 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.EnumSet;
@@ -53,6 +55,7 @@ import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FsServerDefaults;
+import org.apache.hadoop.fs.InvalidPathException;
import org.apache.hadoop.fs.ParentNotDirectoryException;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
@@ -69,7 +72,10 @@ import org.apache.hadoop.hdfs.server.datanode.SimulatedFSDataset;
import org.apache.hadoop.hdfs.server.datanode.fsdataset.FsDatasetSpi;
import org.apache.hadoop.hdfs.server.namenode.FSNamesystem;
import org.apache.hadoop.hdfs.server.namenode.LeaseManager;
+import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocols;
+import org.apache.hadoop.io.EnumSetWritable;
import org.apache.hadoop.io.IOUtils;
+import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.Time;
import org.apache.log4j.Level;
import org.junit.Ignore;
@@ -93,6 +99,15 @@ public class TestFileCreation {
static final int numBlocks = 2;
static final int fileSize = numBlocks * blockSize + 1;
boolean simulatedStorage = false;
+
+ private static final String[] NON_CANONICAL_PATHS = new String[] {
+ "//foo",
+ "///foo2",
+ "//dir//file",
+ "////test2/file",
+ "/dir/./file2",
+ "/dir/../file3"
+ };
// creates a file but does not close it
public static FSDataOutputStream createFile(FileSystem fileSys, Path name, int repl)
@@ -988,4 +1003,93 @@ public class TestFileCreation {
}
}
}
+
+ /**
+ * Regression test for HDFS-3626. Creates a file using a non-canonical path
+ * (i.e. with extra slashes between components) and makes sure that the NN
+ * can properly restart.
+ *
+ * This test RPCs directly to the NN, to ensure that even an old client
+ * which passes an invalid path won't cause corrupt edits.
+ */
+ @Test
+ public void testCreateNonCanonicalPathAndRestartRpc() throws Exception {
+ doCreateTest(CreationMethod.DIRECT_NN_RPC);
+ }
+
+ /**
+ * Another regression test for HDFS-3626. This one creates files using
+ * a Path instantiated from a string object.
+ */
+ @Test
+ public void testCreateNonCanonicalPathAndRestartFromString()
+ throws Exception {
+ doCreateTest(CreationMethod.PATH_FROM_STRING);
+ }
+
+ /**
+ * Another regression test for HDFS-3626. This one creates files using
+ * a Path instantiated from a URI object.
+ */
+ @Test
+ public void testCreateNonCanonicalPathAndRestartFromUri()
+ throws Exception {
+ doCreateTest(CreationMethod.PATH_FROM_URI);
+ }
+
+ private static enum CreationMethod {
+ DIRECT_NN_RPC,
+ PATH_FROM_URI,
+ PATH_FROM_STRING
+ };
+ private void doCreateTest(CreationMethod method) throws Exception {
+ Configuration conf = new HdfsConfiguration();
+ MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf)
+ .numDataNodes(1).build();
+ try {
+ FileSystem fs = cluster.getFileSystem();
+ NamenodeProtocols nnrpc = cluster.getNameNodeRpc();
+
+ for (String pathStr : NON_CANONICAL_PATHS) {
+ System.out.println("Creating " + pathStr + " by " + method);
+ switch (method) {
+ case DIRECT_NN_RPC:
+ try {
+ nnrpc.create(pathStr, new FsPermission((short)0755), "client",
+ new EnumSetWritable(EnumSet.of(CreateFlag.CREATE)),
+ true, (short)1, 128*1024*1024L);
+ fail("Should have thrown exception when creating '"
+ + pathStr + "'" + " by " + method);
+ } catch (InvalidPathException ipe) {
+ // When we create by direct NN RPC, the NN just rejects the
+ // non-canonical paths, rather than trying to normalize them.
+ // So, we expect all of them to fail.
+ }
+ break;
+
+ case PATH_FROM_URI:
+ case PATH_FROM_STRING:
+ // Unlike the above direct-to-NN case, we expect these to succeed,
+ // since the Path constructor should normalize the path.
+ Path p;
+ if (method == CreationMethod.PATH_FROM_URI) {
+ p = new Path(new URI(fs.getUri() + pathStr));
+ } else {
+ p = new Path(fs.getUri() + pathStr);
+ }
+ FSDataOutputStream stm = fs.create(p);
+ IOUtils.closeStream(stm);
+ break;
+ default:
+ throw new AssertionError("bad method: " + method);
+ }
+ }
+
+ cluster.restartNameNode();
+
+ } finally {
+ cluster.shutdown();
+ }
+ }
+
}
From 06b394dcf90d38a724ef9b8779a507d0b196d358 Mon Sep 17 00:00:00 2001
From: Todd Lipcon
Date: Wed, 25 Jul 2012 22:04:59 +0000
Subject: [PATCH 18/39] Amend previous commit of HDFS-3626: accidentally
included a hunk from HADOOP-8621 in svn commit. Reverting that hunk
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365817 13f79535-47bb-0310-9956-ffa450edef68
---
.../src/main/java/org/apache/hadoop/fs/FileUtil.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java
index fd5166c69a6..ba9bb4eafee 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java
@@ -611,7 +611,7 @@ public class FileUtil {
*/
public static int symLink(String target, String linkname) throws IOException{
String cmd = "ln -s " + target + " " + linkname;
- Process p = Runtime.getRuntime().exec(new String[]{"ln","-s",target,linkname}, null);
+ Process p = Runtime.getRuntime().exec(cmd, null);
int returnVal = -1;
try{
returnVal = p.waitFor();
From 8f395c2f78e5813e613197c3078a4ebc5d42775a Mon Sep 17 00:00:00 2001
From: Tsz-wo Sze
Date: Wed, 25 Jul 2012 23:37:25 +0000
Subject: [PATCH 19/39] HDFS-3696. Set chunked streaming mode in
WebHdfsFileSystem write operations to get around a Java library bug causing
OutOfMemoryError.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365839 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 3 +
.../hadoop/hdfs/web/WebHdfsFileSystem.java | 18 +-
.../apache/hadoop/hdfs/web/TestWebHDFS.java | 199 ++++++++++++++++++
3 files changed, 206 insertions(+), 14 deletions(-)
create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHDFS.java
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index e955ea34ecc..5d1e01ef65d 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -1403,6 +1403,9 @@ Release 0.23.3 - UNRELEASED
HDFS-3646. LeaseRenewer can hold reference to inactive DFSClient
instances forever. (Kihwal Lee via daryn)
+ HDFS-3696. Set chunked streaming mode in WebHdfsFileSystem write operations
+ to get around a Java library bug causing OutOfMemoryError. (szetszwo)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/WebHdfsFileSystem.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/WebHdfsFileSystem.java
index 4327dd18c5b..420d74d5829 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/WebHdfsFileSystem.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/WebHdfsFileSystem.java
@@ -423,6 +423,7 @@ public class WebHdfsFileSystem extends FileSystem
//Step 2) Submit another Http request with the URL from the Location header with data.
conn = (HttpURLConnection)new URL(redirect).openConnection();
conn.setRequestMethod(op.getType().toString());
+ conn.setChunkedStreamingMode(32 << 10); //32kB-chunk
return conn;
}
@@ -835,8 +836,7 @@ public class WebHdfsFileSystem extends FileSystem
}
private static WebHdfsFileSystem getWebHdfs(
- final Token> token, final Configuration conf
- ) throws IOException, InterruptedException, URISyntaxException {
+ final Token> token, final Configuration conf) throws IOException {
final InetSocketAddress nnAddr = SecurityUtil.getTokenServiceAddr(token);
final URI uri = DFSUtil.createUri(WebHdfsFileSystem.SCHEME, nnAddr);
@@ -850,12 +850,7 @@ public class WebHdfsFileSystem extends FileSystem
// update the kerberos credentials, if they are coming from a keytab
ugi.reloginFromKeytab();
- try {
- WebHdfsFileSystem webhdfs = getWebHdfs(token, conf);
- return webhdfs.renewDelegationToken(token);
- } catch (URISyntaxException e) {
- throw new IOException(e);
- }
+ return getWebHdfs(token, conf).renewDelegationToken(token);
}
@Override
@@ -865,12 +860,7 @@ public class WebHdfsFileSystem extends FileSystem
// update the kerberos credentials, if they are coming from a keytab
ugi.checkTGTAndReloginFromKeytab();
- try {
- final WebHdfsFileSystem webhdfs = getWebHdfs(token, conf);
- webhdfs.cancelDelegationToken(token);
- } catch (URISyntaxException e) {
- throw new IOException(e);
- }
+ getWebHdfs(token, conf).cancelDelegationToken(token);
}
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHDFS.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHDFS.java
new file mode 100644
index 00000000000..66f65cc2792
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHDFS.java
@@ -0,0 +1,199 @@
+/**
+ * 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.hdfs.web;
+
+import java.io.IOException;
+import java.util.Random;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataInputStream;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.junit.Assert;
+import org.junit.Test;
+
+/** Test WebHDFS */
+public class TestWebHDFS {
+ static final Log LOG = LogFactory.getLog(TestWebHDFS.class);
+
+ static final Random RANDOM = new Random();
+
+ static final long systemStartTime = System.nanoTime();
+
+ /** A timer for measuring performance. */
+ static class Ticker {
+ final String name;
+ final long startTime = System.nanoTime();
+ private long previousTick = startTime;
+
+ Ticker(final String name, String format, Object... args) {
+ this.name = name;
+ LOG.info(String.format("\n\n%s START: %s\n",
+ name, String.format(format, args)));
+ }
+
+ void tick(final long nBytes, String format, Object... args) {
+ final long now = System.nanoTime();
+ if (now - previousTick > 10000000000L) {
+ previousTick = now;
+ final double mintues = (now - systemStartTime)/60000000000.0;
+ LOG.info(String.format("\n\n%s %.2f min) %s %s\n", name, mintues,
+ String.format(format, args), toMpsString(nBytes, now)));
+ }
+ }
+
+ void end(final long nBytes) {
+ final long now = System.nanoTime();
+ final double seconds = (now - startTime)/1000000000.0;
+ LOG.info(String.format("\n\n%s END: duration=%.2fs %s\n",
+ name, seconds, toMpsString(nBytes, now)));
+ }
+
+ String toMpsString(final long nBytes, final long now) {
+ final double mb = nBytes/(double)(1<<20);
+ final double mps = mb*1000000000.0/(now - startTime);
+ return String.format("[nBytes=%.2fMB, speed=%.2fMB/s]", mb, mps);
+ }
+ }
+
+ @Test
+ public void testLargeFile() throws Exception {
+ largeFileTest(200L << 20); //200MB file length
+ }
+
+ /** Test read and write large files. */
+ static void largeFileTest(final long fileLength) throws Exception {
+ final Configuration conf = WebHdfsTestUtil.createConf();
+
+ final MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf)
+ .numDataNodes(3)
+ .build();
+ try {
+ cluster.waitActive();
+
+ final FileSystem fs = WebHdfsTestUtil.getWebHdfsFileSystem(conf);
+ final Path dir = new Path("/test/largeFile");
+ Assert.assertTrue(fs.mkdirs(dir));
+
+ final byte[] data = new byte[1 << 20];
+ RANDOM.nextBytes(data);
+
+ final byte[] expected = new byte[2 * data.length];
+ System.arraycopy(data, 0, expected, 0, data.length);
+ System.arraycopy(data, 0, expected, data.length, data.length);
+
+ final Path p = new Path(dir, "file");
+ final Ticker t = new Ticker("WRITE", "fileLength=" + fileLength);
+ final FSDataOutputStream out = fs.create(p);
+ try {
+ long remaining = fileLength;
+ for(; remaining > 0;) {
+ t.tick(fileLength - remaining, "remaining=%d", remaining);
+
+ final int n = (int)Math.min(remaining, data.length);
+ out.write(data, 0, n);
+ remaining -= n;
+ }
+ } finally {
+ out.close();
+ }
+ t.end(fileLength);
+
+ Assert.assertEquals(fileLength, fs.getFileStatus(p).getLen());
+
+ final long smallOffset = RANDOM.nextInt(1 << 20) + (1 << 20);
+ final long largeOffset = fileLength - smallOffset;
+ final byte[] buf = new byte[data.length];
+
+ verifySeek(fs, p, largeOffset, fileLength, buf, expected);
+ verifySeek(fs, p, smallOffset, fileLength, buf, expected);
+
+ verifyPread(fs, p, largeOffset, fileLength, buf, expected);
+ } finally {
+ cluster.shutdown();
+ }
+ }
+
+ static void checkData(long offset, long remaining, int n,
+ byte[] actual, byte[] expected) {
+ if (RANDOM.nextInt(100) == 0) {
+ int j = (int)(offset % actual.length);
+ for(int i = 0; i < n; i++) {
+ if (expected[j] != actual[i]) {
+ Assert.fail("expected[" + j + "]=" + expected[j]
+ + " != actual[" + i + "]=" + actual[i]
+ + ", offset=" + offset + ", remaining=" + remaining + ", n=" + n);
+ }
+ j++;
+ }
+ }
+ }
+
+ /** test seek */
+ static void verifySeek(FileSystem fs, Path p, long offset, long length,
+ byte[] buf, byte[] expected) throws IOException {
+ long remaining = length - offset;
+ long checked = 0;
+ LOG.info("XXX SEEK: offset=" + offset + ", remaining=" + remaining);
+
+ final Ticker t = new Ticker("SEEK", "offset=%d, remaining=%d",
+ offset, remaining);
+ final FSDataInputStream in = fs.open(p, 64 << 10);
+ in.seek(offset);
+ for(; remaining > 0; ) {
+ t.tick(checked, "offset=%d, remaining=%d", offset, remaining);
+ final int n = (int)Math.min(remaining, buf.length);
+ in.readFully(buf, 0, n);
+ checkData(offset, remaining, n, buf, expected);
+
+ offset += n;
+ remaining -= n;
+ checked += n;
+ }
+ in.close();
+ t.end(checked);
+ }
+
+ static void verifyPread(FileSystem fs, Path p, long offset, long length,
+ byte[] buf, byte[] expected) throws IOException {
+ long remaining = length - offset;
+ long checked = 0;
+ LOG.info("XXX PREAD: offset=" + offset + ", remaining=" + remaining);
+
+ final Ticker t = new Ticker("PREAD", "offset=%d, remaining=%d",
+ offset, remaining);
+ final FSDataInputStream in = fs.open(p, 64 << 10);
+ for(; remaining > 0; ) {
+ t.tick(checked, "offset=%d, remaining=%d", offset, remaining);
+ final int n = (int)Math.min(remaining, buf.length);
+ in.readFully(offset, buf, 0, n);
+ checkData(offset, remaining, n, buf, expected);
+
+ offset += n;
+ remaining -= n;
+ checked += n;
+ }
+ in.close();
+ t.end(checked);
+ }
+}
From 9d16c9354b0c05edb30d23003dcdec4cc44ed925 Mon Sep 17 00:00:00 2001
From: Alejandro Abdelnur
Date: Thu, 26 Jul 2012 13:23:05 +0000
Subject: [PATCH 20/39] MAPREDUCE-4417. add support for encrypted shuffle
(tucu)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365979 13f79535-47bb-0310-9956-ffa450edef68
---
.../src/main/conf/ssl-client.xml.example | 27 +-
.../src/main/conf/ssl-server.xml.example | 26 +-
.../ssl/FileBasedKeyStoresFactory.java | 241 ++++++++
.../hadoop/security/ssl/KeyStoresFactory.java | 67 ++
.../ssl/ReloadingX509TrustManager.java | 204 ++++++
.../hadoop/security/ssl/SSLFactory.java | 237 +++++++
.../security/ssl/SSLHostnameVerifier.java | 585 ++++++++++++++++++
.../src/main/resources/core-default.xml | 47 ++
.../hadoop/security/ssl/KeyStoreTestUtil.java | 270 ++++++++
.../ssl/TestReloadingX509TrustManager.java | 175 ++++++
.../hadoop/security/ssl/TestSSLFactory.java | 164 +++++
hadoop-mapreduce-project/CHANGES.txt | 2 +
.../dev-support/findbugs-exclude.xml | 7 +-
.../mapreduce/v2/app/job/impl/TaskImpl.java | 14 +-
.../org/apache/hadoop/mapreduce/MRConfig.java | 5 +
.../hadoop/mapreduce/task/reduce/Fetcher.java | 40 +-
.../src/main/resources/mapred-default.xml | 15 +
.../security/ssl/TestEncryptedShuffle.java | 184 ++++++
.../apache/hadoop/mapred/ShuffleHandler.java | 87 ++-
.../src/site/apt/EncryptedShuffle.apt.vm | 320 ++++++++++
.../src/site/apt/index.apt.vm | 2 +
hadoop-project/src/site/site.xml | 1 +
22 files changed, 2688 insertions(+), 32 deletions(-)
create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java
create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/KeyStoresFactory.java
create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/ReloadingX509TrustManager.java
create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java
create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLHostnameVerifier.java
create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/KeyStoreTestUtil.java
create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestReloadingX509TrustManager.java
create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java
create mode 100644 hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/security/ssl/TestEncryptedShuffle.java
create mode 100644 hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/EncryptedShuffle.apt.vm
diff --git a/hadoop-common-project/hadoop-common/src/main/conf/ssl-client.xml.example b/hadoop-common-project/hadoop-common/src/main/conf/ssl-client.xml.example
index ec3fd41fa81..a50dce48c23 100644
--- a/hadoop-common-project/hadoop-common/src/main/conf/ssl-client.xml.example
+++ b/hadoop-common-project/hadoop-common/src/main/conf/ssl-client.xml.example
@@ -1,6 +1,21 @@
+
@@ -21,7 +36,15 @@
ssl.client.truststore.type
jks
- Optional. Default value is "jks".
+ Optional. The keystore file format, default value is "jks".
+
+
+
+
+ ssl.client.truststore.reload.interval
+ 10000
+ Truststore reload check interval, in milliseconds.
+ Default value is 10000 (10 seconds).
@@ -50,7 +73,7 @@
ssl.client.keystore.type
jks
- Optional. Default value is "jks".
+ Optional. The keystore file format, default value is "jks".
diff --git a/hadoop-common-project/hadoop-common/src/main/conf/ssl-server.xml.example b/hadoop-common-project/hadoop-common/src/main/conf/ssl-server.xml.example
index 22e9cb0ebbf..4b363ff21f7 100644
--- a/hadoop-common-project/hadoop-common/src/main/conf/ssl-server.xml.example
+++ b/hadoop-common-project/hadoop-common/src/main/conf/ssl-server.xml.example
@@ -1,6 +1,21 @@
+
@@ -20,10 +35,17 @@
ssl.server.truststore.type
jks
- Optional. Default value is "jks".
+ Optional. The keystore file format, default value is "jks".
+
+ ssl.server.truststore.reload.interval
+ 10000
+ Truststore reload check interval, in milliseconds.
+ Default value is 10000 (10 seconds).
+
+
ssl.server.keystore.location
@@ -48,7 +70,7 @@
ssl.server.keystore.type
jks
- Optional. Default value is "jks".
+ Optional. The keystore file format, default value is "jks".
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java
new file mode 100644
index 00000000000..00dd2021eec
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java
@@ -0,0 +1,241 @@
+/**
+* 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.security.ssl;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManager;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.text.MessageFormat;
+
+/**
+ * {@link KeyStoresFactory} implementation that reads the certificates from
+ * keystore files.
+ *
+ * if the trust certificates keystore file changes, the {@link TrustManager}
+ * is refreshed with the new trust certificate entries (using a
+ * {@link ReloadingX509TrustManager} trustmanager).
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public class FileBasedKeyStoresFactory implements KeyStoresFactory {
+
+ private static final Log LOG =
+ LogFactory.getLog(FileBasedKeyStoresFactory.class);
+
+ public static final String SSL_KEYSTORE_LOCATION_TPL_KEY =
+ "ssl.{0}.keystore.location";
+ public static final String SSL_KEYSTORE_PASSWORD_TPL_KEY =
+ "ssl.{0}.keystore.password";
+ public static final String SSL_KEYSTORE_TYPE_TPL_KEY =
+ "ssl.{0}.keystore.type";
+
+ public static final String SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY =
+ "ssl.{0}.truststore.reload.interval";
+ public static final String SSL_TRUSTSTORE_LOCATION_TPL_KEY =
+ "ssl.{0}.truststore.location";
+ public static final String SSL_TRUSTSTORE_PASSWORD_TPL_KEY =
+ "ssl.{0}.truststore.password";
+ public static final String SSL_TRUSTSTORE_TYPE_TPL_KEY =
+ "ssl.{0}.truststore.type";
+
+ /**
+ * Default format of the keystore files.
+ */
+ public static final String DEFAULT_KEYSTORE_TYPE = "jks";
+
+ /**
+ * Reload interval in milliseconds.
+ */
+ public static final int DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL = 10000;
+
+ private Configuration conf;
+ private KeyManager[] keyManagers;
+ private TrustManager[] trustManagers;
+ private ReloadingX509TrustManager trustManager;
+
+ /**
+ * Resolves a property name to its client/server version if applicable.
+ *
+ * NOTE: This method is public for testing purposes.
+ *
+ * @param mode client/server mode.
+ * @param template property name template.
+ * @return the resolved property name.
+ */
+ @VisibleForTesting
+ public static String resolvePropertyName(SSLFactory.Mode mode,
+ String template) {
+ return MessageFormat.format(template, mode.toString().toLowerCase());
+ }
+
+ /**
+ * Sets the configuration for the factory.
+ *
+ * @param conf the configuration for the factory.
+ */
+ @Override
+ public void setConf(Configuration conf) {
+ this.conf = conf;
+ }
+
+ /**
+ * Returns the configuration of the factory.
+ *
+ * @return the configuration of the factory.
+ */
+ @Override
+ public Configuration getConf() {
+ return conf;
+ }
+
+ /**
+ * Initializes the keystores of the factory.
+ *
+ * @param mode if the keystores are to be used in client or server mode.
+ * @throws IOException thrown if the keystores could not be initialized due
+ * to an IO error.
+ * @throws GeneralSecurityException thrown if the keystores could not be
+ * initialized due to a security error.
+ */
+ public void init(SSLFactory.Mode mode)
+ throws IOException, GeneralSecurityException {
+
+ boolean requireClientCert =
+ conf.getBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, true);
+
+ // certificate store
+ String keystoreType =
+ conf.get(resolvePropertyName(mode, SSL_KEYSTORE_TYPE_TPL_KEY),
+ DEFAULT_KEYSTORE_TYPE);
+ KeyStore keystore = KeyStore.getInstance(keystoreType);
+ String keystorePassword = null;
+ if (requireClientCert || mode == SSLFactory.Mode.SERVER) {
+ String locationProperty =
+ resolvePropertyName(mode, SSL_KEYSTORE_LOCATION_TPL_KEY);
+ String keystoreLocation = conf.get(locationProperty, "");
+ if (keystoreLocation.isEmpty()) {
+ throw new GeneralSecurityException("The property '" + locationProperty +
+ "' has not been set in the ssl configuration file.");
+ }
+ String passwordProperty =
+ resolvePropertyName(mode, SSL_KEYSTORE_PASSWORD_TPL_KEY);
+ keystorePassword = conf.get(passwordProperty, "");
+ if (keystorePassword.isEmpty()) {
+ throw new GeneralSecurityException("The property '" + passwordProperty +
+ "' has not been set in the ssl configuration file.");
+ }
+ LOG.debug(mode.toString() + " KeyStore: " + keystoreLocation);
+
+ InputStream is = new FileInputStream(keystoreLocation);
+ try {
+ keystore.load(is, keystorePassword.toCharArray());
+ } finally {
+ is.close();
+ }
+ LOG.info(mode.toString() + " Loaded KeyStore: " + keystoreLocation);
+ } else {
+ keystore.load(null, null);
+ }
+ KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance("SunX509");
+ keyMgrFactory.init(keystore, (keystorePassword != null) ?
+ keystorePassword.toCharArray() : null);
+ keyManagers = keyMgrFactory.getKeyManagers();
+
+ //trust store
+ String truststoreType =
+ conf.get(resolvePropertyName(mode, SSL_TRUSTSTORE_TYPE_TPL_KEY),
+ DEFAULT_KEYSTORE_TYPE);
+
+ String locationProperty =
+ resolvePropertyName(mode, SSL_TRUSTSTORE_LOCATION_TPL_KEY);
+ String truststoreLocation = conf.get(locationProperty, "");
+ if (truststoreLocation.isEmpty()) {
+ throw new GeneralSecurityException("The property '" + locationProperty +
+ "' has not been set in the ssl configuration file.");
+ }
+
+ String passwordProperty = resolvePropertyName(mode,
+ SSL_TRUSTSTORE_PASSWORD_TPL_KEY);
+ String truststorePassword = conf.get(passwordProperty, "");
+ if (truststorePassword.isEmpty()) {
+ throw new GeneralSecurityException("The property '" + passwordProperty +
+ "' has not been set in the ssl configuration file.");
+ }
+ long truststoreReloadInterval =
+ conf.getLong(
+ resolvePropertyName(mode, SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY),
+ DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL);
+
+ LOG.debug(mode.toString() + " TrustStore: " + truststoreLocation);
+
+ trustManager = new ReloadingX509TrustManager(truststoreType,
+ truststoreLocation,
+ truststorePassword,
+ truststoreReloadInterval);
+ trustManager.init();
+ LOG.info(mode.toString() + " Loaded TrustStore: " + truststoreLocation);
+
+ trustManagers = new TrustManager[]{trustManager};
+ }
+
+ /**
+ * Releases any resources being used.
+ */
+ @Override
+ public synchronized void destroy() {
+ if (trustManager != null) {
+ trustManager.destroy();
+ trustManager = null;
+ keyManagers = null;
+ trustManagers = null;
+ }
+ }
+
+ /**
+ * Returns the keymanagers for owned certificates.
+ *
+ * @return the keymanagers for owned certificates.
+ */
+ @Override
+ public KeyManager[] getKeyManagers() {
+ return keyManagers;
+ }
+
+ /**
+ * Returns the trustmanagers for trusted certificates.
+ *
+ * @return the trustmanagers for trusted certificates.
+ */
+ @Override
+ public TrustManager[] getTrustManagers() {
+ return trustManagers;
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/KeyStoresFactory.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/KeyStoresFactory.java
new file mode 100644
index 00000000000..25d2c543362
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/KeyStoresFactory.java
@@ -0,0 +1,67 @@
+/**
+* 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.security.ssl;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configurable;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.TrustManager;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/**
+ * Interface that gives access to {@link KeyManager} and {@link TrustManager}
+ * implementations.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public interface KeyStoresFactory extends Configurable {
+
+ /**
+ * Initializes the keystores of the factory.
+ *
+ * @param mode if the keystores are to be used in client or server mode.
+ * @throws IOException thrown if the keystores could not be initialized due
+ * to an IO error.
+ * @throws GeneralSecurityException thrown if the keystores could not be
+ * initialized due to an security error.
+ */
+ public void init(SSLFactory.Mode mode) throws IOException, GeneralSecurityException;
+
+ /**
+ * Releases any resources being used.
+ */
+ public void destroy();
+
+ /**
+ * Returns the keymanagers for owned certificates.
+ *
+ * @return the keymanagers for owned certificates.
+ */
+ public KeyManager[] getKeyManagers();
+
+ /**
+ * Returns the trustmanagers for trusted certificates.
+ *
+ * @return the trustmanagers for trusted certificates.
+ */
+ public TrustManager[] getTrustManagers();
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/ReloadingX509TrustManager.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/ReloadingX509TrustManager.java
new file mode 100644
index 00000000000..58cdf00175f
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/ReloadingX509TrustManager.java
@@ -0,0 +1,204 @@
+/**
+ * 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.security.ssl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A {@link TrustManager} implementation that reloads its configuration when
+ * the truststore file on disk changes.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public final class ReloadingX509TrustManager
+ implements X509TrustManager, Runnable {
+
+ private static final Log LOG =
+ LogFactory.getLog(ReloadingX509TrustManager.class);
+
+ private String type;
+ private File file;
+ private String password;
+ private long lastLoaded;
+ private long reloadInterval;
+ private AtomicReference trustManagerRef;
+
+ private volatile boolean running;
+ private Thread reloader;
+
+ /**
+ * Creates a reloadable trustmanager. The trustmanager reloads itself
+ * if the underlying trustore file has changed.
+ *
+ * @param type type of truststore file, typically 'jks'.
+ * @param location local path to the truststore file.
+ * @param password password of the truststore file.
+ * @param reloadInterval interval to check if the truststore file has
+ * changed, in milliseconds.
+ * @throws IOException thrown if the truststore could not be initialized due
+ * to an IO error.
+ * @throws GeneralSecurityException thrown if the truststore could not be
+ * initialized due to a security error.
+ */
+ public ReloadingX509TrustManager(String type, String location,
+ String password, long reloadInterval)
+ throws IOException, GeneralSecurityException {
+ this.type = type;
+ file = new File(location);
+ this.password = password;
+ trustManagerRef = new AtomicReference();
+ trustManagerRef.set(loadTrustManager());
+ this.reloadInterval = reloadInterval;
+ }
+
+ /**
+ * Starts the reloader thread.
+ */
+ public void init() {
+ reloader = new Thread(this, "Truststore reloader thread");
+ reloader.setDaemon(true);
+ running = true;
+ reloader.start();
+ }
+
+ /**
+ * Stops the reloader thread.
+ */
+ public void destroy() {
+ running = false;
+ reloader.interrupt();
+ }
+
+ /**
+ * Returns the reload check interval.
+ *
+ * @return the reload check interval, in milliseconds.
+ */
+ public long getReloadInterval() {
+ return reloadInterval;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ X509TrustManager tm = trustManagerRef.get();
+ if (tm != null) {
+ tm.checkClientTrusted(chain, authType);
+ } else {
+ throw new CertificateException("Unknown client chain certificate: " +
+ chain[0].toString());
+ }
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
+ X509TrustManager tm = trustManagerRef.get();
+ if (tm != null) {
+ tm.checkServerTrusted(chain, authType);
+ } else {
+ throw new CertificateException("Unknown server chain certificate: " +
+ chain[0].toString());
+ }
+ }
+
+ private static final X509Certificate[] EMPTY = new X509Certificate[0];
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ X509Certificate[] issuers = EMPTY;
+ X509TrustManager tm = trustManagerRef.get();
+ if (tm != null) {
+ issuers = tm.getAcceptedIssuers();
+ }
+ return issuers;
+ }
+
+ boolean needsReload() {
+ boolean reload = true;
+ if (file.exists()) {
+ if (file.lastModified() == lastLoaded) {
+ reload = false;
+ }
+ } else {
+ lastLoaded = 0;
+ }
+ return reload;
+ }
+
+ X509TrustManager loadTrustManager()
+ throws IOException, GeneralSecurityException {
+ X509TrustManager trustManager = null;
+ KeyStore ks = KeyStore.getInstance(type);
+ lastLoaded = file.lastModified();
+ FileInputStream in = new FileInputStream(file);
+ try {
+ ks.load(in, password.toCharArray());
+ LOG.debug("Loaded truststore '" + file + "'");
+ } finally {
+ in.close();
+ }
+
+ TrustManagerFactory trustManagerFactory =
+ TrustManagerFactory.getInstance("SunX509");
+ trustManagerFactory.init(ks);
+ TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+ for (TrustManager trustManager1 : trustManagers) {
+ if (trustManager1 instanceof X509TrustManager) {
+ trustManager = (X509TrustManager) trustManager1;
+ break;
+ }
+ }
+ return trustManager;
+ }
+
+ @Override
+ public void run() {
+ while (running) {
+ try {
+ Thread.sleep(reloadInterval);
+ } catch (InterruptedException e) {
+ //NOP
+ }
+ if (running && needsReload()) {
+ try {
+ trustManagerRef.set(loadTrustManager());
+ } catch (Exception ex) {
+ LOG.warn("Could not load truststore (keep using existing one) : " +
+ ex.toString(), ex);
+ }
+ }
+ }
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java
new file mode 100644
index 00000000000..adab8b6395a
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLFactory.java
@@ -0,0 +1,237 @@
+/**
+* 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.security.ssl;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.util.ReflectionUtils;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/**
+ * Factory that creates SSLEngine and SSLSocketFactory instances using
+ * Hadoop configuration information.
+ *
+ * This SSLFactory uses a {@link ReloadingX509TrustManager} instance,
+ * which reloads public keys if the truststore file changes.
+ *
+ * This factory is used to configure HTTPS in Hadoop HTTP based endpoints, both
+ * client and server.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public class SSLFactory {
+
+ @InterfaceAudience.Private
+ public static enum Mode { CLIENT, SERVER }
+
+ public static final String SSL_REQUIRE_CLIENT_CERT_KEY =
+ "hadoop.ssl.require.client.cert";
+ public static final String SSL_HOSTNAME_VERIFIER_KEY =
+ "hadoop.ssl.hostname.verifier";
+ public static final String SSL_CLIENT_CONF_KEY =
+ "hadoop.ssl.client.conf";
+ public static final String SSL_SERVER_CONF_KEY =
+ "hadoop.ssl.server.conf";
+
+ public static final boolean DEFAULT_SSL_REQUIRE_CLIENT_CERT = false;
+
+ public static final String KEYSTORES_FACTORY_CLASS_KEY =
+ "hadoop.ssl.keystores.factory.class";
+
+ private Configuration conf;
+ private Mode mode;
+ private boolean requireClientCert;
+ private SSLContext context;
+ private HostnameVerifier hostnameVerifier;
+ private KeyStoresFactory keystoresFactory;
+
+ /**
+ * Creates an SSLFactory.
+ *
+ * @param mode SSLFactory mode, client or server.
+ * @param conf Hadoop configuration from where the SSLFactory configuration
+ * will be read.
+ */
+ public SSLFactory(Mode mode, Configuration conf) {
+ this.conf = conf;
+ if (mode == null) {
+ throw new IllegalArgumentException("mode cannot be NULL");
+ }
+ this.mode = mode;
+ requireClientCert = conf.getBoolean(SSL_REQUIRE_CLIENT_CERT_KEY,
+ DEFAULT_SSL_REQUIRE_CLIENT_CERT);
+ Configuration sslConf = readSSLConfiguration(mode);
+
+ Class extends KeyStoresFactory> klass
+ = conf.getClass(KEYSTORES_FACTORY_CLASS_KEY,
+ FileBasedKeyStoresFactory.class, KeyStoresFactory.class);
+ keystoresFactory = ReflectionUtils.newInstance(klass, sslConf);
+ }
+
+ private Configuration readSSLConfiguration(Mode mode) {
+ Configuration sslConf = new Configuration(false);
+ sslConf.setBoolean(SSL_REQUIRE_CLIENT_CERT_KEY, requireClientCert);
+ String sslConfResource;
+ if (mode == Mode.CLIENT) {
+ sslConfResource = conf.get(SSL_CLIENT_CONF_KEY, "ssl-client.xml");
+ } else {
+ sslConfResource = conf.get(SSL_SERVER_CONF_KEY, "ssl-server.xml");
+ }
+ sslConf.addResource(sslConfResource);
+ return sslConf;
+ }
+
+ /**
+ * Initializes the factory.
+ *
+ * @throws GeneralSecurityException thrown if an SSL initialization error
+ * happened.
+ * @throws IOException thrown if an IO error happened while reading the SSL
+ * configuration.
+ */
+ public void init() throws GeneralSecurityException, IOException {
+ keystoresFactory.init(mode);
+ context = SSLContext.getInstance("TLS");
+ context.init(keystoresFactory.getKeyManagers(),
+ keystoresFactory.getTrustManagers(), null);
+
+ hostnameVerifier = getHostnameVerifier(conf);
+ }
+
+ private HostnameVerifier getHostnameVerifier(Configuration conf)
+ throws GeneralSecurityException, IOException {
+ HostnameVerifier hostnameVerifier;
+ String verifier =
+ conf.get(SSL_HOSTNAME_VERIFIER_KEY, "DEFAULT").trim().toUpperCase();
+ if (verifier.equals("DEFAULT")) {
+ hostnameVerifier = SSLHostnameVerifier.DEFAULT;
+ } else if (verifier.equals("DEFAULT_AND_LOCALHOST")) {
+ hostnameVerifier = SSLHostnameVerifier.DEFAULT_AND_LOCALHOST;
+ } else if (verifier.equals("STRICT")) {
+ hostnameVerifier = SSLHostnameVerifier.STRICT;
+ } else if (verifier.equals("STRICT_IE6")) {
+ hostnameVerifier = SSLHostnameVerifier.STRICT_IE6;
+ } else if (verifier.equals("ALLOW_ALL")) {
+ hostnameVerifier = SSLHostnameVerifier.ALLOW_ALL;
+ } else {
+ throw new GeneralSecurityException("Invalid hostname verifier: " +
+ verifier);
+ }
+ return hostnameVerifier;
+ }
+
+ /**
+ * Releases any resources being used.
+ */
+ public void destroy() {
+ keystoresFactory.destroy();
+ }
+ /**
+ * Returns the SSLFactory KeyStoresFactory instance.
+ *
+ * @return the SSLFactory KeyStoresFactory instance.
+ */
+ public KeyStoresFactory getKeystoresFactory() {
+ return keystoresFactory;
+ }
+
+ /**
+ * Returns a configured SSLEngine.
+ *
+ * @return the configured SSLEngine.
+ * @throws GeneralSecurityException thrown if the SSL engine could not
+ * be initialized.
+ * @throws IOException thrown if and IO error occurred while loading
+ * the server keystore.
+ */
+ public SSLEngine createSSLEngine()
+ throws GeneralSecurityException, IOException {
+ SSLEngine sslEngine = context.createSSLEngine();
+ if (mode == Mode.CLIENT) {
+ sslEngine.setUseClientMode(true);
+ } else {
+ sslEngine.setUseClientMode(false);
+ sslEngine.setNeedClientAuth(requireClientCert);
+ }
+ return sslEngine;
+ }
+
+ /**
+ * Returns a configured SSLServerSocketFactory.
+ *
+ * @return the configured SSLSocketFactory.
+ * @throws GeneralSecurityException thrown if the SSLSocketFactory could not
+ * be initialized.
+ * @throws IOException thrown if and IO error occurred while loading
+ * the server keystore.
+ */
+ public SSLServerSocketFactory createSSLServerSocketFactory()
+ throws GeneralSecurityException, IOException {
+ if (mode != Mode.SERVER) {
+ throw new IllegalStateException("Factory is in CLIENT mode");
+ }
+ return context.getServerSocketFactory();
+ }
+
+ /**
+ * Returns a configured SSLSocketFactory.
+ *
+ * @return the configured SSLSocketFactory.
+ * @throws GeneralSecurityException thrown if the SSLSocketFactory could not
+ * be initialized.
+ * @throws IOException thrown if and IO error occurred while loading
+ * the server keystore.
+ */
+ public SSLSocketFactory createSSLSocketFactory()
+ throws GeneralSecurityException, IOException {
+ if (mode != Mode.CLIENT) {
+ throw new IllegalStateException("Factory is in CLIENT mode");
+ }
+ return context.getSocketFactory();
+ }
+
+ /**
+ * Returns the hostname verifier it should be used in HttpsURLConnections.
+ *
+ * @return the hostname verifier.
+ */
+ public HostnameVerifier getHostnameVerifier() {
+ if (mode != Mode.CLIENT) {
+ throw new IllegalStateException("Factory is in CLIENT mode");
+ }
+ return hostnameVerifier;
+ }
+
+ /**
+ * Returns if client certificates are required or not.
+ *
+ * @return if client certificates are required or not.
+ */
+ public boolean isClientCertRequired() {
+ return requireClientCert;
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLHostnameVerifier.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLHostnameVerifier.java
new file mode 100644
index 00000000000..3f88fb89a7d
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/SSLHostnameVerifier.java
@@ -0,0 +1,585 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hadoop.security.ssl;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.classification.InterfaceStability;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+/**
+ ************************************************************************
+ * Copied from the not-yet-commons-ssl project at
+ * http://juliusdavies.ca/commons-ssl/
+ * This project is not yet in Apache, but it is Apache 2.0 licensed.
+ ************************************************************************
+ * Interface for checking if a hostname matches the names stored inside the
+ * server's X.509 certificate. Correctly implements
+ * javax.net.ssl.HostnameVerifier, but that interface is not recommended.
+ * Instead we added several check() methods that take SSLSocket,
+ * or X509Certificate, or ultimately (they all end up calling this one),
+ * String. (It's easier to supply JUnit with Strings instead of mock
+ * SSLSession objects!)
+ *
Our check() methods throw exceptions if the name is
+ * invalid, whereas javax.net.ssl.HostnameVerifier just returns true/false.
+ *
+ * We provide the HostnameVerifier.DEFAULT, HostnameVerifier.STRICT, and
+ * HostnameVerifier.ALLOW_ALL implementations. We also provide the more
+ * specialized HostnameVerifier.DEFAULT_AND_LOCALHOST, as well as
+ * HostnameVerifier.STRICT_IE6. But feel free to define your own
+ * implementations!
+ *
+ * Inspired by Sebastian Hauer's original StrictSSLProtocolSocketFactory in the
+ * HttpClient "contrib" repository.
+ */
+@InterfaceAudience.Private
+@InterfaceStability.Evolving
+public interface SSLHostnameVerifier extends javax.net.ssl.HostnameVerifier {
+
+ boolean verify(String host, SSLSession session);
+
+ void check(String host, SSLSocket ssl) throws IOException;
+
+ void check(String host, X509Certificate cert) throws SSLException;
+
+ void check(String host, String[] cns, String[] subjectAlts)
+ throws SSLException;
+
+ void check(String[] hosts, SSLSocket ssl) throws IOException;
+
+ void check(String[] hosts, X509Certificate cert) throws SSLException;
+
+
+ /**
+ * Checks to see if the supplied hostname matches any of the supplied CNs
+ * or "DNS" Subject-Alts. Most implementations only look at the first CN,
+ * and ignore any additional CNs. Most implementations do look at all of
+ * the "DNS" Subject-Alts. The CNs or Subject-Alts may contain wildcards
+ * according to RFC 2818.
+ *
+ * @param cns CN fields, in order, as extracted from the X.509
+ * certificate.
+ * @param subjectAlts Subject-Alt fields of type 2 ("DNS"), as extracted
+ * from the X.509 certificate.
+ * @param hosts The array of hostnames to verify.
+ * @throws SSLException If verification failed.
+ */
+ void check(String[] hosts, String[] cns, String[] subjectAlts)
+ throws SSLException;
+
+
+ /**
+ * The DEFAULT HostnameVerifier works the same way as Curl and Firefox.
+ *
+ * The hostname must match either the first CN, or any of the subject-alts.
+ * A wildcard can occur in the CN, and in any of the subject-alts.
+ *
+ * The only difference between DEFAULT and STRICT is that a wildcard (such
+ * as "*.foo.com") with DEFAULT matches all subdomains, including
+ * "a.b.foo.com".
+ */
+ public final static SSLHostnameVerifier DEFAULT =
+ new AbstractVerifier() {
+ public final void check(final String[] hosts, final String[] cns,
+ final String[] subjectAlts)
+ throws SSLException {
+ check(hosts, cns, subjectAlts, false, false);
+ }
+
+ public final String toString() { return "DEFAULT"; }
+ };
+
+
+ /**
+ * The DEFAULT_AND_LOCALHOST HostnameVerifier works like the DEFAULT
+ * one with one additional relaxation: a host of "localhost",
+ * "localhost.localdomain", "127.0.0.1", "::1" will always pass, no matter
+ * what is in the server's certificate.
+ */
+ public final static SSLHostnameVerifier DEFAULT_AND_LOCALHOST =
+ new AbstractVerifier() {
+ public final void check(final String[] hosts, final String[] cns,
+ final String[] subjectAlts)
+ throws SSLException {
+ if (isLocalhost(hosts[0])) {
+ return;
+ }
+ check(hosts, cns, subjectAlts, false, false);
+ }
+
+ public final String toString() { return "DEFAULT_AND_LOCALHOST"; }
+ };
+
+ /**
+ * The STRICT HostnameVerifier works the same way as java.net.URL in Sun
+ * Java 1.4, Sun Java 5, Sun Java 6. It's also pretty close to IE6.
+ * This implementation appears to be compliant with RFC 2818 for dealing
+ * with wildcards.
+ *
+ * The hostname must match either the first CN, or any of the subject-alts.
+ * A wildcard can occur in the CN, and in any of the subject-alts. The
+ * one divergence from IE6 is how we only check the first CN. IE6 allows
+ * a match against any of the CNs present. We decided to follow in
+ * Sun Java 1.4's footsteps and only check the first CN.
+ *
+ * A wildcard such as "*.foo.com" matches only subdomains in the same
+ * level, for example "a.foo.com". It does not match deeper subdomains
+ * such as "a.b.foo.com".
+ */
+ public final static SSLHostnameVerifier STRICT =
+ new AbstractVerifier() {
+ public final void check(final String[] host, final String[] cns,
+ final String[] subjectAlts)
+ throws SSLException {
+ check(host, cns, subjectAlts, false, true);
+ }
+
+ public final String toString() { return "STRICT"; }
+ };
+
+ /**
+ * The STRICT_IE6 HostnameVerifier works just like the STRICT one with one
+ * minor variation: the hostname can match against any of the CN's in the
+ * server's certificate, not just the first one. This behaviour is
+ * identical to IE6's behaviour.
+ */
+ public final static SSLHostnameVerifier STRICT_IE6 =
+ new AbstractVerifier() {
+ public final void check(final String[] host, final String[] cns,
+ final String[] subjectAlts)
+ throws SSLException {
+ check(host, cns, subjectAlts, true, true);
+ }
+
+ public final String toString() { return "STRICT_IE6"; }
+ };
+
+ /**
+ * The ALLOW_ALL HostnameVerifier essentially turns hostname verification
+ * off. This implementation is a no-op, and never throws the SSLException.
+ */
+ public final static SSLHostnameVerifier ALLOW_ALL =
+ new AbstractVerifier() {
+ public final void check(final String[] host, final String[] cns,
+ final String[] subjectAlts) {
+ // Allow everything - so never blowup.
+ }
+
+ public final String toString() { return "ALLOW_ALL"; }
+ };
+
+ @SuppressWarnings("unchecked")
+ abstract class AbstractVerifier implements SSLHostnameVerifier {
+
+ /**
+ * This contains a list of 2nd-level domains that aren't allowed to
+ * have wildcards when combined with country-codes.
+ * For example: [*.co.uk].
+ *
+ * The [*.co.uk] problem is an interesting one. Should we just hope
+ * that CA's would never foolishly allow such a certificate to happen?
+ * Looks like we're the only implementation guarding against this.
+ * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check.
+ */
+ private final static String[] BAD_COUNTRY_2LDS =
+ {"ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
+ "lg", "ne", "net", "or", "org"};
+
+ private final static String[] LOCALHOSTS = {"::1", "127.0.0.1",
+ "localhost",
+ "localhost.localdomain"};
+
+
+ static {
+ // Just in case developer forgot to manually sort the array. :-)
+ Arrays.sort(BAD_COUNTRY_2LDS);
+ Arrays.sort(LOCALHOSTS);
+ }
+
+ protected AbstractVerifier() {}
+
+ /**
+ * The javax.net.ssl.HostnameVerifier contract.
+ *
+ * @param host 'hostname' we used to create our socket
+ * @param session SSLSession with the remote server
+ * @return true if the host matched the one in the certificate.
+ */
+ public boolean verify(String host, SSLSession session) {
+ try {
+ Certificate[] certs = session.getPeerCertificates();
+ X509Certificate x509 = (X509Certificate) certs[0];
+ check(new String[]{host}, x509);
+ return true;
+ }
+ catch (SSLException e) {
+ return false;
+ }
+ }
+
+ public void check(String host, SSLSocket ssl) throws IOException {
+ check(new String[]{host}, ssl);
+ }
+
+ public void check(String host, X509Certificate cert)
+ throws SSLException {
+ check(new String[]{host}, cert);
+ }
+
+ public void check(String host, String[] cns, String[] subjectAlts)
+ throws SSLException {
+ check(new String[]{host}, cns, subjectAlts);
+ }
+
+ public void check(String host[], SSLSocket ssl)
+ throws IOException {
+ if (host == null) {
+ throw new NullPointerException("host to verify is null");
+ }
+
+ SSLSession session = ssl.getSession();
+ if (session == null) {
+ // In our experience this only happens under IBM 1.4.x when
+ // spurious (unrelated) certificates show up in the server'
+ // chain. Hopefully this will unearth the real problem:
+ InputStream in = ssl.getInputStream();
+ in.available();
+ /*
+ If you're looking at the 2 lines of code above because
+ you're running into a problem, you probably have two
+ options:
+
+ #1. Clean up the certificate chain that your server
+ is presenting (e.g. edit "/etc/apache2/server.crt"
+ or wherever it is your server's certificate chain
+ is defined).
+
+ OR
+
+ #2. Upgrade to an IBM 1.5.x or greater JVM, or switch
+ to a non-IBM JVM.
+ */
+
+ // If ssl.getInputStream().available() didn't cause an
+ // exception, maybe at least now the session is available?
+ session = ssl.getSession();
+ if (session == null) {
+ // If it's still null, probably a startHandshake() will
+ // unearth the real problem.
+ ssl.startHandshake();
+
+ // Okay, if we still haven't managed to cause an exception,
+ // might as well go for the NPE. Or maybe we're okay now?
+ session = ssl.getSession();
+ }
+ }
+ Certificate[] certs;
+ try {
+ certs = session.getPeerCertificates();
+ } catch (SSLPeerUnverifiedException spue) {
+ InputStream in = ssl.getInputStream();
+ in.available();
+ // Didn't trigger anything interesting? Okay, just throw
+ // original.
+ throw spue;
+ }
+ X509Certificate x509 = (X509Certificate) certs[0];
+ check(host, x509);
+ }
+
+ public void check(String[] host, X509Certificate cert)
+ throws SSLException {
+ String[] cns = Certificates.getCNs(cert);
+ String[] subjectAlts = Certificates.getDNSSubjectAlts(cert);
+ check(host, cns, subjectAlts);
+ }
+
+ public void check(final String[] hosts, final String[] cns,
+ final String[] subjectAlts, final boolean ie6,
+ final boolean strictWithSubDomains)
+ throws SSLException {
+ // Build up lists of allowed hosts For logging/debugging purposes.
+ StringBuffer buf = new StringBuffer(32);
+ buf.append('<');
+ for (int i = 0; i < hosts.length; i++) {
+ String h = hosts[i];
+ h = h != null ? h.trim().toLowerCase() : "";
+ hosts[i] = h;
+ if (i > 0) {
+ buf.append('/');
+ }
+ buf.append(h);
+ }
+ buf.append('>');
+ String hostnames = buf.toString();
+ // Build the list of names we're going to check. Our DEFAULT and
+ // STRICT implementations of the HostnameVerifier only use the
+ // first CN provided. All other CNs are ignored.
+ // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).
+ TreeSet names = new TreeSet();
+ if (cns != null && cns.length > 0 && cns[0] != null) {
+ names.add(cns[0]);
+ if (ie6) {
+ for (int i = 1; i < cns.length; i++) {
+ names.add(cns[i]);
+ }
+ }
+ }
+ if (subjectAlts != null) {
+ for (int i = 0; i < subjectAlts.length; i++) {
+ if (subjectAlts[i] != null) {
+ names.add(subjectAlts[i]);
+ }
+ }
+ }
+ if (names.isEmpty()) {
+ String msg = "Certificate for " + hosts[0] + " doesn't contain CN or DNS subjectAlt";
+ throw new SSLException(msg);
+ }
+
+ // StringBuffer for building the error message.
+ buf = new StringBuffer();
+
+ boolean match = false;
+ out:
+ for (Iterator it = names.iterator(); it.hasNext();) {
+ // Don't trim the CN, though!
+ String cn = (String) it.next();
+ cn = cn.toLowerCase();
+ // Store CN in StringBuffer in case we need to report an error.
+ buf.append(" <");
+ buf.append(cn);
+ buf.append('>');
+ if (it.hasNext()) {
+ buf.append(" OR");
+ }
+
+ // The CN better have at least two dots if it wants wildcard
+ // action. It also can't be [*.co.uk] or [*.co.jp] or
+ // [*.org.uk], etc...
+ boolean doWildcard = cn.startsWith("*.") &&
+ cn.lastIndexOf('.') >= 0 &&
+ !isIP4Address(cn) &&
+ acceptableCountryWildcard(cn);
+
+ for (int i = 0; i < hosts.length; i++) {
+ final String hostName = hosts[i].trim().toLowerCase();
+ if (doWildcard) {
+ match = hostName.endsWith(cn.substring(1));
+ if (match && strictWithSubDomains) {
+ // If we're in strict mode, then [*.foo.com] is not
+ // allowed to match [a.b.foo.com]
+ match = countDots(hostName) == countDots(cn);
+ }
+ } else {
+ match = hostName.equals(cn);
+ }
+ if (match) {
+ break out;
+ }
+ }
+ }
+ if (!match) {
+ throw new SSLException("hostname in certificate didn't match: " + hostnames + " !=" + buf);
+ }
+ }
+
+ public static boolean isIP4Address(final String cn) {
+ boolean isIP4 = true;
+ String tld = cn;
+ int x = cn.lastIndexOf('.');
+ // We only bother analyzing the characters after the final dot
+ // in the name.
+ if (x >= 0 && x + 1 < cn.length()) {
+ tld = cn.substring(x + 1);
+ }
+ for (int i = 0; i < tld.length(); i++) {
+ if (!Character.isDigit(tld.charAt(0))) {
+ isIP4 = false;
+ break;
+ }
+ }
+ return isIP4;
+ }
+
+ public static boolean acceptableCountryWildcard(final String cn) {
+ int cnLen = cn.length();
+ if (cnLen >= 7 && cnLen <= 9) {
+ // Look for the '.' in the 3rd-last position:
+ if (cn.charAt(cnLen - 3) == '.') {
+ // Trim off the [*.] and the [.XX].
+ String s = cn.substring(2, cnLen - 3);
+ // And test against the sorted array of bad 2lds:
+ int x = Arrays.binarySearch(BAD_COUNTRY_2LDS, s);
+ return x < 0;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isLocalhost(String host) {
+ host = host != null ? host.trim().toLowerCase() : "";
+ if (host.startsWith("::1")) {
+ int x = host.lastIndexOf('%');
+ if (x >= 0) {
+ host = host.substring(0, x);
+ }
+ }
+ int x = Arrays.binarySearch(LOCALHOSTS, host);
+ return x >= 0;
+ }
+
+ /**
+ * Counts the number of dots "." in a string.
+ *
+ * @param s string to count dots from
+ * @return number of dots
+ */
+ public static int countDots(final String s) {
+ int count = 0;
+ for (int i = 0; i < s.length(); i++) {
+ if (s.charAt(i) == '.') {
+ count++;
+ }
+ }
+ return count;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ static class Certificates {
+ public static String[] getCNs(X509Certificate cert) {
+ LinkedList cnList = new LinkedList();
+ /*
+ Sebastian Hauer's original StrictSSLProtocolSocketFactory used
+ getName() and had the following comment:
+
+ Parses a X.500 distinguished name for the value of the
+ "Common Name" field. This is done a bit sloppy right
+ now and should probably be done a bit more according to
+ RFC 2253
.
+
+ I've noticed that toString() seems to do a better job than
+ getName() on these X500Principal objects, so I'm hoping that
+ addresses Sebastian's concern.
+
+ For example, getName() gives me this:
+ 1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d
+
+ whereas toString() gives me this:
+ EMAILADDRESS=juliusdavies@cucbc.com
+
+ Looks like toString() even works with non-ascii domain names!
+ I tested it with "花子.co.jp" and it worked fine.
+ */
+ String subjectPrincipal = cert.getSubjectX500Principal().toString();
+ StringTokenizer st = new StringTokenizer(subjectPrincipal, ",");
+ while (st.hasMoreTokens()) {
+ String tok = st.nextToken();
+ int x = tok.indexOf("CN=");
+ if (x >= 0) {
+ cnList.add(tok.substring(x + 3));
+ }
+ }
+ if (!cnList.isEmpty()) {
+ String[] cns = new String[cnList.size()];
+ cnList.toArray(cns);
+ return cns;
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Extracts the array of SubjectAlt DNS names from an X509Certificate.
+ * Returns null if there aren't any.
+ *
+ * Note: Java doesn't appear able to extract international characters
+ * from the SubjectAlts. It can only extract international characters
+ * from the CN field.
+ *
+ * (Or maybe the version of OpenSSL I'm using to test isn't storing the
+ * international characters correctly in the SubjectAlts?).
+ *
+ * @param cert X509Certificate
+ * @return Array of SubjectALT DNS names stored in the certificate.
+ */
+ public static String[] getDNSSubjectAlts(X509Certificate cert) {
+ LinkedList subjectAltList = new LinkedList();
+ Collection c = null;
+ try {
+ c = cert.getSubjectAlternativeNames();
+ }
+ catch (CertificateParsingException cpe) {
+ // Should probably log.debug() this?
+ cpe.printStackTrace();
+ }
+ if (c != null) {
+ Iterator it = c.iterator();
+ while (it.hasNext()) {
+ List list = (List) it.next();
+ int type = ((Integer) list.get(0)).intValue();
+ // If type is 2, then we've got a dNSName
+ if (type == 2) {
+ String s = (String) list.get(1);
+ subjectAltList.add(s);
+ }
+ }
+ }
+ if (!subjectAltList.isEmpty()) {
+ String[] subjectAlts = new String[subjectAltList.size()];
+ subjectAltList.toArray(subjectAlts);
+ return subjectAlts;
+ } else {
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
index ce28a2b2942..7667eecb244 100644
--- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
+++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
@@ -1026,4 +1026,51 @@
hadoop.http.staticuser.user
dr.who
+
+
+
+
+ hadoop.ssl.keystores.factory.class
+ org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory
+
+ The keystores factory to use for retrieving certificates.
+
+
+
+
+ hadoop.ssl.require.client.cert
+ false
+ Whether client certificates are required
+
+
+
+ hadoop.ssl.hostname.verifier
+ DEFAULT
+
+ The hostname verifier to provide for HttpsURLConnections.
+ Valid values are: DEFAULT, STRICT, STRICT_I6, DEFAULT_AND_LOCALHOST and
+ ALLOW_ALL
+
+
+
+
+ hadoop.ssl.server.conf
+ ssl-server.xml
+
+ Resource file from which ssl server keystore information will be extracted.
+ This file is looked up in the classpath, typically it should be in Hadoop
+ conf/ directory.
+
+
+
+
+ hadoop.ssl.client.conf
+ ssl-client.xml
+
+ Resource file from which ssl client keystore information will be extracted
+ This file is looked up in the classpath, typically it should be in Hadoop
+ conf/ directory.
+
+
+
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/KeyStoreTestUtil.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/KeyStoreTestUtil.java
new file mode 100644
index 00000000000..c57cbfdd96e
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/KeyStoreTestUtil.java
@@ -0,0 +1,270 @@
+/**
+ * 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.security.ssl;
+
+import org.apache.hadoop.conf.Configuration;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.CertificateAlgorithmId;
+import sun.security.x509.CertificateIssuerName;
+import sun.security.x509.CertificateSerialNumber;
+import sun.security.x509.CertificateSubjectName;
+import sun.security.x509.CertificateValidity;
+import sun.security.x509.CertificateVersion;
+import sun.security.x509.CertificateX509Key;
+import sun.security.x509.X500Name;
+import sun.security.x509.X509CertImpl;
+import sun.security.x509.X509CertInfo;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.math.BigInteger;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class KeyStoreTestUtil {
+
+ public static String getClasspathDir(Class klass) throws Exception {
+ String file = klass.getName();
+ file = file.replace('.', '/') + ".class";
+ URL url = Thread.currentThread().getContextClassLoader().getResource(file);
+ String baseDir = url.toURI().getPath();
+ baseDir = baseDir.substring(0, baseDir.length() - file.length() - 1);
+ return baseDir;
+ }
+
+ /**
+ * Create a self-signed X.509 Certificate.
+ * From http://bfo.com/blog/2011/03/08/odds_and_ends_creating_a_new_x_509_certificate.html.
+ *
+ * @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
+ * @param pair the KeyPair
+ * @param days how many days from now the Certificate is valid for
+ * @param algorithm the signing algorithm, eg "SHA1withRSA"
+ * @return the self-signed certificate
+ * @throws IOException thrown if an IO error ocurred.
+ * @throws GeneralSecurityException thrown if an Security error ocurred.
+ */
+ public static X509Certificate generateCertificate(String dn, KeyPair pair,
+ int days, String algorithm)
+ throws GeneralSecurityException, IOException {
+ PrivateKey privkey = pair.getPrivate();
+ X509CertInfo info = new X509CertInfo();
+ Date from = new Date();
+ Date to = new Date(from.getTime() + days * 86400000l);
+ CertificateValidity interval = new CertificateValidity(from, to);
+ BigInteger sn = new BigInteger(64, new SecureRandom());
+ X500Name owner = new X500Name(dn);
+
+ info.set(X509CertInfo.VALIDITY, interval);
+ info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
+ info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
+ info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
+ info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
+ info
+ .set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
+ AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
+ info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
+
+ // Sign the cert to identify the algorithm that's used.
+ X509CertImpl cert = new X509CertImpl(info);
+ cert.sign(privkey, algorithm);
+
+ // Update the algorith, and resign.
+ algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG);
+ info
+ .set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM,
+ algo);
+ cert = new X509CertImpl(info);
+ cert.sign(privkey, algorithm);
+ return cert;
+ }
+
+ public static KeyPair generateKeyPair(String algorithm)
+ throws NoSuchAlgorithmException {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
+ keyGen.initialize(1024);
+ return keyGen.genKeyPair();
+ }
+
+ private static KeyStore createEmptyKeyStore()
+ throws GeneralSecurityException, IOException {
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(null, null); // initialize
+ return ks;
+ }
+
+ private static void saveKeyStore(KeyStore ks, String filename,
+ String password)
+ throws GeneralSecurityException, IOException {
+ FileOutputStream out = new FileOutputStream(filename);
+ try {
+ ks.store(out, password.toCharArray());
+ } finally {
+ out.close();
+ }
+ }
+
+ public static void createKeyStore(String filename,
+ String password, String alias,
+ Key privateKey, Certificate cert)
+ throws GeneralSecurityException, IOException {
+ KeyStore ks = createEmptyKeyStore();
+ ks.setKeyEntry(alias, privateKey, password.toCharArray(),
+ new Certificate[]{cert});
+ saveKeyStore(ks, filename, password);
+ }
+
+ public static void createTrustStore(String filename,
+ String password, String alias,
+ Certificate cert)
+ throws GeneralSecurityException, IOException {
+ KeyStore ks = createEmptyKeyStore();
+ ks.setCertificateEntry(alias, cert);
+ saveKeyStore(ks, filename, password);
+ }
+
+ public static void createTrustStore(
+ String filename, String password, Map certs)
+ throws GeneralSecurityException, IOException {
+ KeyStore ks = createEmptyKeyStore();
+ for (Map.Entry cert : certs.entrySet()) {
+ ks.setCertificateEntry(cert.getKey(), cert.getValue());
+ }
+ saveKeyStore(ks, filename, password);
+ }
+
+ public static void cleanupSSLConfig(String keystoresDir, String sslConfDir)
+ throws Exception {
+ File f = new File(keystoresDir + "/clientKS.jks");
+ f.delete();
+ f = new File(keystoresDir + "/serverKS.jks");
+ f.delete();
+ f = new File(keystoresDir + "/trustKS.jks");
+ f.delete();
+ f = new File(sslConfDir + "/ssl-client.xml");
+ f.delete();
+ f = new File(sslConfDir + "/ssl-server.xml");
+ f.delete();
+ }
+
+ public static void setupSSLConfig(String keystoresDir, String sslConfDir,
+ Configuration conf, boolean useClientCert)
+ throws Exception {
+ String clientKS = keystoresDir + "/clientKS.jks";
+ String clientPassword = "clientP";
+ String serverKS = keystoresDir + "/serverKS.jks";
+ String serverPassword = "serverP";
+ String trustKS = keystoresDir + "/trustKS.jks";
+ String trustPassword = "trustP";
+
+ File sslClientConfFile = new File(sslConfDir + "/ssl-client.xml");
+ File sslServerConfFile = new File(sslConfDir + "/ssl-server.xml");
+
+ Map certs = new HashMap();
+
+ if (useClientCert) {
+ KeyPair cKP = KeyStoreTestUtil.generateKeyPair("RSA");
+ X509Certificate cCert =
+ KeyStoreTestUtil.generateCertificate("CN=localhost, O=client", cKP, 30,
+ "SHA1withRSA");
+ KeyStoreTestUtil.createKeyStore(clientKS, clientPassword, "client",
+ cKP.getPrivate(), cCert);
+ certs.put("client", cCert);
+ }
+
+ KeyPair sKP = KeyStoreTestUtil.generateKeyPair("RSA");
+ X509Certificate sCert =
+ KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", sKP, 30,
+ "SHA1withRSA");
+ KeyStoreTestUtil.createKeyStore(serverKS, serverPassword, "server",
+ sKP.getPrivate(), sCert);
+ certs.put("server", sCert);
+
+ KeyStoreTestUtil.createTrustStore(trustKS, trustPassword, certs);
+
+ Configuration clientSSLConf = new Configuration(false);
+ clientSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.CLIENT,
+ FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), clientKS);
+ clientSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.CLIENT,
+ FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), clientPassword);
+ clientSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.CLIENT,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS);
+ clientSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.CLIENT,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY), trustPassword);
+ clientSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.CLIENT,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000");
+
+ Configuration serverSSLConf = new Configuration(false);
+ serverSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.SERVER,
+ FileBasedKeyStoresFactory.SSL_KEYSTORE_LOCATION_TPL_KEY), serverKS);
+ serverSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.SERVER,
+ FileBasedKeyStoresFactory.SSL_KEYSTORE_PASSWORD_TPL_KEY), serverPassword);
+ serverSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.SERVER,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_LOCATION_TPL_KEY), trustKS);
+ serverSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.SERVER,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_PASSWORD_TPL_KEY), trustPassword);
+ serverSSLConf.set(FileBasedKeyStoresFactory.resolvePropertyName(
+ SSLFactory.Mode.SERVER,
+ FileBasedKeyStoresFactory.SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY), "1000");
+
+ Writer writer = new FileWriter(sslClientConfFile);
+ try {
+ clientSSLConf.writeXml(writer);
+ } finally {
+ writer.close();
+ }
+
+ writer = new FileWriter(sslServerConfFile);
+ try {
+ serverSSLConf.writeXml(writer);
+ } finally {
+ writer.close();
+ }
+
+ conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL");
+ conf.set(SSLFactory.SSL_CLIENT_CONF_KEY, sslClientConfFile.getName());
+ conf.set(SSLFactory.SSL_SERVER_CONF_KEY, sslServerConfFile.getName());
+ conf.setBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY, useClientCert);
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestReloadingX509TrustManager.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestReloadingX509TrustManager.java
new file mode 100644
index 00000000000..5a03605f349
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestReloadingX509TrustManager.java
@@ -0,0 +1,175 @@
+/**
+ * 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.security.ssl;
+
+import org.apache.hadoop.fs.FileUtil;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.Map;
+
+import static junit.framework.Assert.assertEquals;
+import static org.apache.hadoop.security.ssl.KeyStoreTestUtil.createTrustStore;
+import static org.apache.hadoop.security.ssl.KeyStoreTestUtil.generateCertificate;
+import static org.apache.hadoop.security.ssl.KeyStoreTestUtil.generateKeyPair;
+
+public class TestReloadingX509TrustManager {
+
+ private static final String BASEDIR =
+ System.getProperty("test.build.data", "target/test-dir") + "/" +
+ TestReloadingX509TrustManager.class.getSimpleName();
+
+ private X509Certificate cert1;
+ private X509Certificate cert2;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ File base = new File(BASEDIR);
+ FileUtil.fullyDelete(base);
+ base.mkdirs();
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadMissingTrustStore() throws Exception {
+ String truststoreLocation = BASEDIR + "/testmissing.jks";
+
+ ReloadingX509TrustManager tm =
+ new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
+ try {
+ tm.init();
+ } finally {
+ tm.destroy();
+ }
+ }
+
+ @Test(expected = IOException.class)
+ public void testLoadCorruptTrustStore() throws Exception {
+ String truststoreLocation = BASEDIR + "/testcorrupt.jks";
+ OutputStream os = new FileOutputStream(truststoreLocation);
+ os.write(1);
+ os.close();
+
+ ReloadingX509TrustManager tm =
+ new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
+ try {
+ tm.init();
+ } finally {
+ tm.destroy();
+ }
+ }
+
+ @Test
+ public void testReload() throws Exception {
+ KeyPair kp = generateKeyPair("RSA");
+ cert1 = generateCertificate("CN=Cert1", kp, 30, "SHA1withRSA");
+ cert2 = generateCertificate("CN=Cert2", kp, 30, "SHA1withRSA");
+ String truststoreLocation = BASEDIR + "/testreload.jks";
+ createTrustStore(truststoreLocation, "password", "cert1", cert1);
+
+ ReloadingX509TrustManager tm =
+ new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
+ try {
+ tm.init();
+ assertEquals(1, tm.getAcceptedIssuers().length);
+
+ // Wait so that the file modification time is different
+ Thread.sleep((tm.getReloadInterval() + 1000));
+
+ // Add another cert
+ Map certs = new HashMap();
+ certs.put("cert1", cert1);
+ certs.put("cert2", cert2);
+ createTrustStore(truststoreLocation, "password", certs);
+
+ // and wait to be sure reload has taken place
+ assertEquals(10, tm.getReloadInterval());
+
+ // Wait so that the file modification time is different
+ Thread.sleep((tm.getReloadInterval() + 200));
+
+ assertEquals(2, tm.getAcceptedIssuers().length);
+ } finally {
+ tm.destroy();
+ }
+ }
+
+ @Test
+ public void testReloadMissingTrustStore() throws Exception {
+ KeyPair kp = generateKeyPair("RSA");
+ cert1 = generateCertificate("CN=Cert1", kp, 30, "SHA1withRSA");
+ cert2 = generateCertificate("CN=Cert2", kp, 30, "SHA1withRSA");
+ String truststoreLocation = BASEDIR + "/testmissing.jks";
+ createTrustStore(truststoreLocation, "password", "cert1", cert1);
+
+ ReloadingX509TrustManager tm =
+ new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
+ try {
+ tm.init();
+ assertEquals(1, tm.getAcceptedIssuers().length);
+ X509Certificate cert = tm.getAcceptedIssuers()[0];
+ new File(truststoreLocation).delete();
+
+ // Wait so that the file modification time is different
+ Thread.sleep((tm.getReloadInterval() + 200));
+
+ assertEquals(1, tm.getAcceptedIssuers().length);
+ assertEquals(cert, tm.getAcceptedIssuers()[0]);
+ } finally {
+ tm.destroy();
+ }
+ }
+
+ @Test
+ public void testReloadCorruptTrustStore() throws Exception {
+ KeyPair kp = generateKeyPair("RSA");
+ cert1 = generateCertificate("CN=Cert1", kp, 30, "SHA1withRSA");
+ cert2 = generateCertificate("CN=Cert2", kp, 30, "SHA1withRSA");
+ String truststoreLocation = BASEDIR + "/testcorrupt.jks";
+ createTrustStore(truststoreLocation, "password", "cert1", cert1);
+
+ ReloadingX509TrustManager tm =
+ new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
+ try {
+ tm.init();
+ assertEquals(1, tm.getAcceptedIssuers().length);
+ X509Certificate cert = tm.getAcceptedIssuers()[0];
+
+ OutputStream os = new FileOutputStream(truststoreLocation);
+ os.write(1);
+ os.close();
+ new File(truststoreLocation).setLastModified(System.currentTimeMillis() -
+ 1000);
+
+ // Wait so that the file modification time is different
+ Thread.sleep((tm.getReloadInterval() + 200));
+
+ assertEquals(1, tm.getAcceptedIssuers().length);
+ assertEquals(cert, tm.getAcceptedIssuers()[0]);
+ } finally {
+ tm.destroy();
+ }
+ }
+
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java
new file mode 100644
index 00000000000..fa270a1f3a4
--- /dev/null
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/ssl/TestSSLFactory.java
@@ -0,0 +1,164 @@
+/**
+ * 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.security.ssl;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileUtil;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+
+public class TestSSLFactory {
+
+ private static final String BASEDIR =
+ System.getProperty("test.build.dir", "target/test-dir") + "/" +
+ TestSSLFactory.class.getSimpleName();
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ File base = new File(BASEDIR);
+ FileUtil.fullyDelete(base);
+ base.mkdirs();
+ }
+
+ private Configuration createConfiguration(boolean clientCert)
+ throws Exception {
+ Configuration conf = new Configuration();
+ String keystoresDir = new File(BASEDIR).getAbsolutePath();
+ String sslConfsDir = KeyStoreTestUtil.getClasspathDir(TestSSLFactory.class);
+ KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfsDir, conf, clientCert);
+ return conf;
+ }
+
+ @After
+ @Before
+ public void cleanUp() throws Exception {
+ String keystoresDir = new File(BASEDIR).getAbsolutePath();
+ String sslConfsDir = KeyStoreTestUtil.getClasspathDir(TestSSLFactory.class);
+ KeyStoreTestUtil.cleanupSSLConfig(keystoresDir, sslConfsDir);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void clientMode() throws Exception {
+ Configuration conf = createConfiguration(false);
+ SSLFactory sslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, conf);
+ try {
+ sslFactory.init();
+ Assert.assertNotNull(sslFactory.createSSLSocketFactory());
+ Assert.assertNotNull(sslFactory.getHostnameVerifier());
+ sslFactory.createSSLServerSocketFactory();
+ } finally {
+ sslFactory.destroy();
+ }
+ }
+
+ private void serverMode(boolean clientCert, boolean socket) throws Exception {
+ Configuration conf = createConfiguration(clientCert);
+ SSLFactory sslFactory = new SSLFactory(SSLFactory.Mode.SERVER, conf);
+ try {
+ sslFactory.init();
+ Assert.assertNotNull(sslFactory.createSSLServerSocketFactory());
+ Assert.assertEquals(clientCert, sslFactory.isClientCertRequired());
+ if (socket) {
+ sslFactory.createSSLSocketFactory();
+ } else {
+ sslFactory.getHostnameVerifier();
+ }
+ } finally {
+ sslFactory.destroy();
+ }
+ }
+
+
+ @Test(expected = IllegalStateException.class)
+ public void serverModeWithoutClientCertsSocket() throws Exception {
+ serverMode(false, true);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void serverModeWithClientCertsSocket() throws Exception {
+ serverMode(true, true);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void serverModeWithoutClientCertsVerifier() throws Exception {
+ serverMode(false, false);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void serverModeWithClientCertsVerifier() throws Exception {
+ serverMode(true, false);
+ }
+
+ @Test
+ public void validHostnameVerifier() throws Exception {
+ Configuration conf = createConfiguration(false);
+ conf.unset(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY);
+ SSLFactory sslFactory = new
+ SSLFactory(SSLFactory.Mode.CLIENT, conf);
+ sslFactory.init();
+ Assert.assertEquals("DEFAULT", sslFactory.getHostnameVerifier().toString());
+ sslFactory.destroy();
+
+ conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "ALLOW_ALL");
+ sslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, conf);
+ sslFactory.init();
+ Assert.assertEquals("ALLOW_ALL",
+ sslFactory.getHostnameVerifier().toString());
+ sslFactory.destroy();
+
+ conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "DEFAULT_AND_LOCALHOST");
+ sslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, conf);
+ sslFactory.init();
+ Assert.assertEquals("DEFAULT_AND_LOCALHOST",
+ sslFactory.getHostnameVerifier().toString());
+ sslFactory.destroy();
+
+ conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "STRICT");
+ sslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, conf);
+ sslFactory.init();
+ Assert.assertEquals("STRICT", sslFactory.getHostnameVerifier().toString());
+ sslFactory.destroy();
+
+ conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "STRICT_IE6");
+ sslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, conf);
+ sslFactory.init();
+ Assert.assertEquals("STRICT_IE6",
+ sslFactory.getHostnameVerifier().toString());
+ sslFactory.destroy();
+ }
+
+ @Test(expected = GeneralSecurityException.class)
+ public void invalidHostnameVerifier() throws Exception {
+ Configuration conf = createConfiguration(false);
+ conf.set(SSLFactory.SSL_HOSTNAME_VERIFIER_KEY, "foo");
+ SSLFactory sslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, conf);
+ try {
+ sslFactory.init();
+ } finally {
+ sslFactory.destroy();
+ }
+ }
+
+}
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index 8bc35a5616d..2cc69e6c78d 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -135,6 +135,8 @@ Branch-2 ( Unreleased changes )
MAPREDUCE-987. Exposing MiniDFS and MiniMR clusters as a single process
command-line. (ahmed via tucu)
+ MAPREDUCE-4417. add support for encrypted shuffle (tucu)
+
IMPROVEMENTS
MAPREDUCE-4157. ResourceManager should not kill apps that are well behaved
diff --git a/hadoop-mapreduce-project/dev-support/findbugs-exclude.xml b/hadoop-mapreduce-project/dev-support/findbugs-exclude.xml
index ff3142cd776..73d54038a5f 100644
--- a/hadoop-mapreduce-project/dev-support/findbugs-exclude.xml
+++ b/hadoop-mapreduce-project/dev-support/findbugs-exclude.xml
@@ -473,5 +473,10 @@
-
+
+
+
+
+
+
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/job/impl/TaskImpl.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/job/impl/TaskImpl.java
index cf5d92b4c7c..5e93fa5a5cf 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/job/impl/TaskImpl.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/job/impl/TaskImpl.java
@@ -34,6 +34,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapreduce.Counters;
+import org.apache.hadoop.mapreduce.MRConfig;
import org.apache.hadoop.mapreduce.OutputCommitter;
import org.apache.hadoop.mapreduce.TaskAttemptID;
import org.apache.hadoop.mapreduce.TypeConverter;
@@ -43,6 +44,7 @@ import org.apache.hadoop.mapreduce.jobhistory.JobHistoryParser.TaskInfo;
import org.apache.hadoop.mapreduce.jobhistory.TaskFailedEvent;
import org.apache.hadoop.mapreduce.jobhistory.TaskFinishedEvent;
import org.apache.hadoop.mapreduce.jobhistory.TaskStartedEvent;
+import org.apache.hadoop.security.ssl.SSLFactory;
import org.apache.hadoop.mapreduce.security.token.JobTokenIdentifier;
import org.apache.hadoop.mapreduce.v2.api.records.JobId;
import org.apache.hadoop.mapreduce.v2.api.records.TaskAttemptCompletionEvent;
@@ -108,7 +110,8 @@ public abstract class TaskImpl implements Task, EventHandler {
private long scheduledTime;
private final RecordFactory recordFactory = RecordFactoryProvider.getRecordFactory(null);
-
+
+ protected boolean encryptedShuffle;
protected Credentials credentials;
protected Token jobToken;
@@ -274,6 +277,8 @@ public abstract class TaskImpl implements Task, EventHandler {
this.jobToken = jobToken;
this.metrics = metrics;
this.appContext = appContext;
+ this.encryptedShuffle = conf.getBoolean(MRConfig.SHUFFLE_SSL_ENABLED_KEY,
+ MRConfig.SHUFFLE_SSL_ENABLED_DEFAULT);
// See if this is from a previous generation.
if (completedTasksFromPreviousRun != null
@@ -637,9 +642,10 @@ public abstract class TaskImpl implements Task, EventHandler {
TaskAttemptCompletionEvent tce = recordFactory
.newRecordInstance(TaskAttemptCompletionEvent.class);
tce.setEventId(-1);
- tce.setMapOutputServerAddress("http://"
- + attempt.getNodeHttpAddress().split(":")[0] + ":"
- + attempt.getShufflePort());
+ String scheme = (encryptedShuffle) ? "https://" : "http://";
+ tce.setMapOutputServerAddress(scheme
+ + attempt.getNodeHttpAddress().split(":")[0] + ":"
+ + attempt.getShufflePort());
tce.setStatus(status);
tce.setAttemptId(attempt.getID());
int runTime = 0;
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/MRConfig.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/MRConfig.java
index 82ee5f00a7f..fb9a1ff6f67 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/MRConfig.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/MRConfig.java
@@ -79,4 +79,9 @@ public interface MRConfig {
public static final int MAX_BLOCK_LOCATIONS_DEFAULT = 10;
public static final String MAX_BLOCK_LOCATIONS_KEY =
"mapreduce.job.max.split.locations";
+
+ public static final String SHUFFLE_SSL_ENABLED_KEY =
+ "mapreduce.shuffle.ssl.enabled";
+
+ public static final boolean SHUFFLE_SSL_ENABLED_DEFAULT = false;
}
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
index f3e7fd61c27..64bc43e51b6 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
@@ -25,11 +25,13 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.net.HttpURLConnection;
import java.net.URLConnection;
+import java.security.GeneralSecurityException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.crypto.SecretKey;
+import javax.net.ssl.HttpsURLConnection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -42,9 +44,11 @@ import org.apache.hadoop.mapred.Counters;
import org.apache.hadoop.mapred.IFileInputStream;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.Reporter;
+import org.apache.hadoop.mapreduce.MRConfig;
import org.apache.hadoop.mapreduce.MRJobConfig;
import org.apache.hadoop.mapreduce.TaskAttemptID;
import org.apache.hadoop.mapreduce.security.SecureShuffleUtils;
+import org.apache.hadoop.security.ssl.SSLFactory;
import org.apache.hadoop.mapreduce.task.reduce.MapOutput.Type;
import org.apache.hadoop.util.Progressable;
import org.apache.hadoop.util.ReflectionUtils;
@@ -92,6 +96,9 @@ class Fetcher extends Thread {
private volatile boolean stopped = false;
+ private static boolean sslShuffle;
+ private static SSLFactory sslFactory;
+
public Fetcher(JobConf job, TaskAttemptID reduceId,
ShuffleScheduler scheduler, MergeManager merger,
Reporter reporter, ShuffleClientMetrics metrics,
@@ -135,6 +142,20 @@ class Fetcher extends Thread {
setName("fetcher#" + id);
setDaemon(true);
+
+ synchronized (Fetcher.class) {
+ sslShuffle = job.getBoolean(MRConfig.SHUFFLE_SSL_ENABLED_KEY,
+ MRConfig.SHUFFLE_SSL_ENABLED_DEFAULT);
+ if (sslShuffle && sslFactory == null) {
+ sslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, job);
+ try {
+ sslFactory.init();
+ } catch (Exception ex) {
+ sslFactory.destroy();
+ throw new RuntimeException(ex);
+ }
+ }
+ }
}
public void run() {
@@ -173,8 +194,25 @@ class Fetcher extends Thread {
} catch (InterruptedException ie) {
LOG.warn("Got interrupt while joining " + getName(), ie);
}
+ if (sslFactory != null) {
+ sslFactory.destroy();
+ }
}
+ protected HttpURLConnection openConnection(URL url) throws IOException {
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ if (sslShuffle) {
+ HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
+ try {
+ httpsConn.setSSLSocketFactory(sslFactory.createSSLSocketFactory());
+ } catch (GeneralSecurityException ex) {
+ throw new IOException(ex);
+ }
+ httpsConn.setHostnameVerifier(sslFactory.getHostnameVerifier());
+ }
+ return conn;
+ }
+
/**
* The crux of the matter...
*
@@ -205,7 +243,7 @@ class Fetcher extends Thread {
try {
URL url = getMapOutputURL(host, maps);
- HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+ HttpURLConnection connection = openConnection(url);
// generate hash of the url
String msgToEncode = SecureShuffleUtils.buildMsgFrom(url);
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml
index 8591079b3a8..5fee954bbaf 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml
@@ -512,6 +512,21 @@
single shuffle can consume
+
+ mapreduce.shuffle.ssl.enabled
+ false
+
+ Whether to use SSL for for the Shuffle HTTP endpoints.
+
+
+
+
+ mapreduce.shuffle.ssl.file.buffer.size
+ 65536
+ Buffer size for reading spills from file when using SSL.
+
+
+
mapreduce.reduce.markreset.buffer.percent
0.0
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/security/ssl/TestEncryptedShuffle.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/security/ssl/TestEncryptedShuffle.java
new file mode 100644
index 00000000000..d6a17cf97bd
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/mapreduce/security/ssl/TestEncryptedShuffle.java
@@ -0,0 +1,184 @@
+/**
+ * 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.mapreduce.security.ssl;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.FileUtil;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.mapred.JobClient;
+import org.apache.hadoop.mapred.JobConf;
+import org.apache.hadoop.mapred.MiniMRClientCluster;
+import org.apache.hadoop.mapred.MiniMRClientClusterFactory;
+import org.apache.hadoop.mapred.RunningJob;
+
+import org.apache.hadoop.mapreduce.MRConfig;
+import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.Assert;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.URL;
+
+public class TestEncryptedShuffle {
+
+ private static final String BASEDIR =
+ System.getProperty("test.build.dir", "target/test-dir") + "/" +
+ TestEncryptedShuffle.class.getSimpleName();
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ File base = new File(BASEDIR);
+ FileUtil.fullyDelete(base);
+ base.mkdirs();
+ }
+
+ @Before
+ public void createCustomYarnClasspath() throws Exception {
+ String classpathDir =
+ KeyStoreTestUtil.getClasspathDir(TestEncryptedShuffle.class);
+
+ URL url = Thread.currentThread().getContextClassLoader().
+ getResource("mrapp-generated-classpath");
+ File f = new File(url.getPath());
+ BufferedReader reader = new BufferedReader(new FileReader(f));
+ String cp = reader.readLine();
+ cp = cp + ":" + classpathDir;
+ f = new File(classpathDir, "mrapp-generated-classpath");
+ Writer writer = new FileWriter(f);
+ writer.write(cp);
+ writer.close();
+ new File(classpathDir, "core-site.xml").delete();
+ }
+
+ @After
+ public void cleanUpMiniClusterSpecialConfig() throws Exception {
+ String classpathDir =
+ KeyStoreTestUtil.getClasspathDir(TestEncryptedShuffle.class);
+ new File(classpathDir, "mrapp-generated-classpath").delete();
+ new File(classpathDir, "core-site.xml").delete();
+ String keystoresDir = new File(BASEDIR).getAbsolutePath();
+ KeyStoreTestUtil.cleanupSSLConfig(keystoresDir, classpathDir);
+ }
+
+ private MiniDFSCluster dfsCluster = null;
+ private MiniMRClientCluster mrCluster = null;
+
+ private void startCluster(Configuration conf) throws Exception {
+ if (System.getProperty("hadoop.log.dir") == null) {
+ System.setProperty("hadoop.log.dir", "target/test-dir");
+ }
+ conf.set("dfs.block.access.token.enable", "false");
+ conf.set("dfs.permissions", "true");
+ conf.set("hadoop.security.authentication", "simple");
+ dfsCluster = new MiniDFSCluster(conf, 1, true, null);
+ FileSystem fileSystem = dfsCluster.getFileSystem();
+ fileSystem.mkdirs(new Path("/tmp"));
+ fileSystem.mkdirs(new Path("/user"));
+ fileSystem.mkdirs(new Path("/hadoop/mapred/system"));
+ fileSystem.setPermission(
+ new Path("/tmp"), FsPermission.valueOf("-rwxrwxrwx"));
+ fileSystem.setPermission(
+ new Path("/user"), FsPermission.valueOf("-rwxrwxrwx"));
+ fileSystem.setPermission(
+ new Path("/hadoop/mapred/system"), FsPermission.valueOf("-rwx------"));
+ FileSystem.setDefaultUri(conf, fileSystem.getUri());
+ mrCluster = MiniMRClientClusterFactory.create(this.getClass(), 1, conf);
+
+ // so the minicluster conf is avail to the containers.
+ String classpathDir =
+ KeyStoreTestUtil.getClasspathDir(TestEncryptedShuffle.class);
+ Writer writer = new FileWriter(classpathDir + "/core-site.xml");
+ mrCluster.getConfig().writeXml(writer);
+ writer.close();
+ }
+
+ private void stopCluster() throws Exception {
+ if (mrCluster != null) {
+ mrCluster.stop();
+ }
+ if (dfsCluster != null) {
+ dfsCluster.shutdown();
+ }
+ }
+
+ protected JobConf getJobConf() throws IOException {
+ return new JobConf(mrCluster.getConfig());
+ }
+
+ private void encryptedShuffleWithCerts(boolean useClientCerts)
+ throws Exception {
+ try {
+ Configuration conf = new Configuration();
+ String keystoresDir = new File(BASEDIR).getAbsolutePath();
+ String sslConfsDir =
+ KeyStoreTestUtil.getClasspathDir(TestEncryptedShuffle.class);
+ KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfsDir, conf,
+ useClientCerts);
+ conf.setBoolean(MRConfig.SHUFFLE_SSL_ENABLED_KEY, true);
+ startCluster(conf);
+ FileSystem fs = FileSystem.get(getJobConf());
+ Path inputDir = new Path("input");
+ fs.mkdirs(inputDir);
+ Writer writer =
+ new OutputStreamWriter(fs.create(new Path(inputDir, "data.txt")));
+ writer.write("hello");
+ writer.close();
+
+ Path outputDir = new Path("output", "output");
+
+ JobConf jobConf = new JobConf(getJobConf());
+ jobConf.setInt("mapred.map.tasks", 1);
+ jobConf.setInt("mapred.map.max.attempts", 1);
+ jobConf.setInt("mapred.reduce.max.attempts", 1);
+ jobConf.set("mapred.input.dir", inputDir.toString());
+ jobConf.set("mapred.output.dir", outputDir.toString());
+ JobClient jobClient = new JobClient(jobConf);
+ RunningJob runJob = jobClient.submitJob(jobConf);
+ runJob.waitForCompletion();
+ Assert.assertTrue(runJob.isComplete());
+ Assert.assertTrue(runJob.isSuccessful());
+ } finally {
+ stopCluster();
+ }
+ }
+
+ @Test
+ public void encryptedShuffleWithClientCerts() throws Exception {
+ encryptedShuffleWithCerts(true);
+ }
+
+ @Test
+ public void encryptedShuffleWithoutClientCerts() throws Exception {
+ encryptedShuffleWithCerts(false);
+ }
+
+}
+
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/main/java/org/apache/hadoop/mapred/ShuffleHandler.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/main/java/org/apache/hadoop/mapred/ShuffleHandler.java
index a0fcefe856a..a5717c99772 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/main/java/org/apache/hadoop/mapred/ShuffleHandler.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/main/java/org/apache/hadoop/mapred/ShuffleHandler.java
@@ -55,7 +55,9 @@ import org.apache.hadoop.fs.LocalDirAllocator;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DataInputByteBuffer;
import org.apache.hadoop.io.DataOutputBuffer;
+import org.apache.hadoop.mapreduce.MRConfig;
import org.apache.hadoop.mapreduce.security.SecureShuffleUtils;
+import org.apache.hadoop.security.ssl.SSLFactory;
import org.apache.hadoop.mapreduce.security.token.JobTokenIdentifier;
import org.apache.hadoop.mapreduce.security.token.JobTokenSecretManager;
import org.apache.hadoop.mapreduce.task.reduce.ShuffleHeader;
@@ -101,6 +103,8 @@ import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
+import org.jboss.netty.handler.ssl.SslHandler;
+import org.jboss.netty.handler.stream.ChunkedFile;
import org.jboss.netty.handler.stream.ChunkedWriteHandler;
import org.jboss.netty.util.CharsetUtil;
@@ -114,6 +118,8 @@ public class ShuffleHandler extends AbstractService
private int port;
private ChannelFactory selector;
private final ChannelGroup accepted = new DefaultChannelGroup();
+ private HttpPipelineFactory pipelineFact;
+ private int sslFileBufferSize;
public static final String MAPREDUCE_SHUFFLE_SERVICEID =
"mapreduce.shuffle";
@@ -126,6 +132,11 @@ public class ShuffleHandler extends AbstractService
public static final String SHUFFLE_PORT_CONFIG_KEY = "mapreduce.shuffle.port";
public static final int DEFAULT_SHUFFLE_PORT = 8080;
+ public static final String SUFFLE_SSL_FILE_BUFFER_SIZE_KEY =
+ "mapreduce.shuffle.ssl.file.buffer.size";
+
+ public static final int DEFAULT_SUFFLE_SSL_FILE_BUFFER_SIZE = 60 * 1024;
+
@Metrics(about="Shuffle output metrics", context="mapred")
static class ShuffleMetrics implements ChannelFutureListener {
@Metric("Shuffle output in bytes")
@@ -249,7 +260,11 @@ public class ShuffleHandler extends AbstractService
public synchronized void start() {
Configuration conf = getConfig();
ServerBootstrap bootstrap = new ServerBootstrap(selector);
- HttpPipelineFactory pipelineFact = new HttpPipelineFactory(conf);
+ try {
+ pipelineFact = new HttpPipelineFactory(conf);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
bootstrap.setPipelineFactory(pipelineFact);
port = conf.getInt(SHUFFLE_PORT_CONFIG_KEY, DEFAULT_SHUFFLE_PORT);
Channel ch = bootstrap.bind(new InetSocketAddress(port));
@@ -259,6 +274,9 @@ public class ShuffleHandler extends AbstractService
pipelineFact.SHUFFLE.setPort(port);
LOG.info(getName() + " listening on port " + port);
super.start();
+
+ sslFileBufferSize = conf.getInt(SUFFLE_SSL_FILE_BUFFER_SIZE_KEY,
+ DEFAULT_SUFFLE_SSL_FILE_BUFFER_SIZE);
}
@Override
@@ -266,6 +284,7 @@ public class ShuffleHandler extends AbstractService
accepted.close().awaitUninterruptibly(10, TimeUnit.SECONDS);
ServerBootstrap bootstrap = new ServerBootstrap(selector);
bootstrap.releaseExternalResources();
+ pipelineFact.destroy();
super.stop();
}
@@ -283,22 +302,38 @@ public class ShuffleHandler extends AbstractService
class HttpPipelineFactory implements ChannelPipelineFactory {
final Shuffle SHUFFLE;
+ private SSLFactory sslFactory;
- public HttpPipelineFactory(Configuration conf) {
+ public HttpPipelineFactory(Configuration conf) throws Exception {
SHUFFLE = new Shuffle(conf);
+ if (conf.getBoolean(MRConfig.SHUFFLE_SSL_ENABLED_KEY,
+ MRConfig.SHUFFLE_SSL_ENABLED_DEFAULT)) {
+ sslFactory = new SSLFactory(SSLFactory.Mode.SERVER, conf);
+ sslFactory.init();
+ }
+ }
+
+ public void destroy() {
+ if (sslFactory != null) {
+ sslFactory.destroy();
+ }
}
@Override
public ChannelPipeline getPipeline() throws Exception {
- return Channels.pipeline(
- new HttpRequestDecoder(),
- new HttpChunkAggregator(1 << 16),
- new HttpResponseEncoder(),
- new ChunkedWriteHandler(),
- SHUFFLE);
- // TODO factor security manager into pipeline
- // TODO factor out encode/decode to permit binary shuffle
- // TODO factor out decode of index to permit alt. models
+ ChannelPipeline pipeline = Channels.pipeline();
+ if (sslFactory != null) {
+ pipeline.addLast("ssl", new SslHandler(sslFactory.createSSLEngine()));
+ }
+ pipeline.addLast("decoder", new HttpRequestDecoder());
+ pipeline.addLast("aggregator", new HttpChunkAggregator(1 << 16));
+ pipeline.addLast("encoder", new HttpResponseEncoder());
+ pipeline.addLast("chunking", new ChunkedWriteHandler());
+ pipeline.addLast("shuffle", SHUFFLE);
+ return pipeline;
+ // TODO factor security manager into pipeline
+ // TODO factor out encode/decode to permit binary shuffle
+ // TODO factor out decode of index to permit alt. models
}
}
@@ -483,17 +518,25 @@ public class ShuffleHandler extends AbstractService
LOG.info(spillfile + " not found");
return null;
}
- final FileRegion partition = new DefaultFileRegion(
- spill.getChannel(), info.startOffset, info.partLength);
- ChannelFuture writeFuture = ch.write(partition);
- writeFuture.addListener(new ChannelFutureListener() {
- // TODO error handling; distinguish IO/connection failures,
- // attribute to appropriate spill output
- @Override
- public void operationComplete(ChannelFuture future) {
- partition.releaseExternalResources();
- }
- });
+ ChannelFuture writeFuture;
+ if (ch.getPipeline().get(SslHandler.class) == null) {
+ final FileRegion partition = new DefaultFileRegion(
+ spill.getChannel(), info.startOffset, info.partLength);
+ writeFuture = ch.write(partition);
+ writeFuture.addListener(new ChannelFutureListener() {
+ // TODO error handling; distinguish IO/connection failures,
+ // attribute to appropriate spill output
+ @Override
+ public void operationComplete(ChannelFuture future) {
+ partition.releaseExternalResources();
+ }
+ });
+ } else {
+ // HTTPS cannot be done with zero copy.
+ writeFuture = ch.write(new ChunkedFile(spill, info.startOffset,
+ info.partLength,
+ sslFileBufferSize));
+ }
metrics.shuffleConnections.incr();
metrics.shuffleOutputBytes.incr(info.partLength); // optimistic
return writeFuture;
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/EncryptedShuffle.apt.vm b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/EncryptedShuffle.apt.vm
new file mode 100644
index 00000000000..e05951c1797
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/EncryptedShuffle.apt.vm
@@ -0,0 +1,320 @@
+~~ Licensed 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. See accompanying LICENSE file.
+
+ ---
+ Hadoop Map Reduce Next Generation-${project.version} - Encrypted Shuffle
+ ---
+ ---
+ ${maven.build.timestamp}
+
+Hadoop MapReduce Next Generation - Encrypted Shuffle
+
+ \[ {{{./index.html}Go Back}} \]
+
+* {Introduction}
+
+ The Encrypted Shuffle capability allows encryption of the MapReduce shuffle
+ using HTTPS and with optional client authentication (also known as
+ bi-directional HTTPS, or HTTPS with client certificates). It comprises:
+
+ * A Hadoop configuration setting for toggling the shuffle between HTTP and
+ HTTPS.
+
+ * A Hadoop configuration settings for specifying the keystore and truststore
+ properties (location, type, passwords) used by the shuffle service and the
+ reducers tasks fetching shuffle data.
+
+ * A way to re-load truststores across the cluster (when a node is added or
+ removed).
+
+* {Configuration}
+
+** <> Properties
+
+ To enable encrypted shuffle, set the following properties in core-site.xml of
+ all nodes in the cluster:
+
+*--------------------------------------+---------------------+-----------------+
+| <> | <> | <> |
+*--------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | Whether client certificates are required |
+*--------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | The hostname verifier to provide for HttpsURLConnections. Valid values are: <>, <>, <>, <> and <> |
+*--------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | The KeyStoresFactory implementation to use |
+*--------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | Resource file from which ssl server keystore information will be extracted. This file is looked up in the classpath, typically it should be in Hadoop conf/ directory |
+*--------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | Resource file from which ssl server keystore information will be extracted. This file is looked up in the classpath, typically it should be in Hadoop conf/ directory |
+*--------------------------------------+---------------------+-----------------+
+
+ <> Currently requiring client certificates should be set to false.
+ Refer the {{{ClientCertificates}Client Certificates}} section for details.
+
+ <> All these properties should be marked as final in the cluster
+ configuration files.
+
+*** Example:
+
+------
+ ...
+
+ hadoop.ssl.require.client.cert
+ false
+ true
+
+
+
+ hadoop.ssl.hostname.verifier
+ DEFAULT
+ true
+
+
+
+ hadoop.ssl.keystores.factory.class
+ org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory
+ true
+
+
+
+ hadoop.ssl.server.conf
+ ssl-server.xml
+ true
+
+
+
+ hadoop.ssl.client.conf
+ ssl-client.xml
+ true
+
+ ...
+------
+
+** <<>> Properties
+
+ To enable encrypted shuffle, set the following property in mapred-site.xml
+ of all nodes in the cluster:
+
+*--------------------------------------+---------------------+-----------------+
+| <> | <> | <> |
+*--------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | Whether encrypted shuffle is enabled |
+*--------------------------------------+---------------------+-----------------+
+
+ <> This property should be marked as final in the cluster
+ configuration files.
+
+*** Example:
+
+------
+ ...
+
+ mapreduce.shuffle.ssl.enabled
+ true
+ true
+
+ ...
+------
+
+ The Linux container executor should be set to prevent job tasks from
+ reading the server keystore information and gaining access to the shuffle
+ server certificates.
+
+ Refer to Hadoop Kerberos configuration for details on how to do this.
+
+* {Keystore and Truststore Settings}
+
+ Currently <<>> is the only <<>>
+ implementation. The <<>> implementation uses the
+ following properties, in the <> and <> files,
+ to configure the keystores and truststores.
+
+** <<>> (Shuffle server) Configuration:
+
+ The mapred user should own the <> file and have exclusive
+ read access to it.
+
+*---------------------------------------------+---------------------+-----------------+
+| <> | <> | <> |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | Keystore file type |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | NONE | Keystore file location. The mapred user should own this file and have exclusive read access to it. |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | NONE | Keystore file password |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | Truststore file type |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | NONE | Truststore file location. The mapred user should own this file and have exclusive read access to it. |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | NONE | Truststore file password |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | 10000 | Truststore reload interval, in milliseconds |
+*--------------------------------------+----------------------------+-----------------+
+
+*** Example:
+
+------
+
+
+
+
+ ssl.server.keystore.type
+ jks
+
+
+ ssl.server.keystore.location
+ ${user.home}/keystores/server-keystore.jks
+
+
+ ssl.server.keystore.password
+ serverfoo
+
+
+
+
+ ssl.server.truststore.type
+ jks
+
+
+ ssl.server.truststore.location
+ ${user.home}/keystores/truststore.jks
+
+
+ ssl.server.truststore.password
+ clientserverbar
+
+
+ ssl.server.truststore.reload.interval
+ 10000
+
+
+------
+
+** <<>> (Reducer/Fetcher) Configuration:
+
+ The mapred user should own the <> file and it should have
+ default permissions.
+
+*---------------------------------------------+---------------------+-----------------+
+| <> | <> | <> |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | Keystore file type |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | NONE | Keystore file location. The mapred user should own this file and it should have default permissions. |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | NONE | Keystore file password |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | <<>> | Truststore file type |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | NONE | Truststore file location. The mapred user should own this file and it should have default permissions. |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | NONE | Truststore file password |
+*---------------------------------------------+---------------------+-----------------+
+| <<>> | 10000 | Truststore reload interval, in milliseconds |
+*--------------------------------------+----------------------------+-----------------+
+
+*** Example:
+
+------
+
+
+
+
+ ssl.client.keystore.type
+ jks
+
+
+ ssl.client.keystore.location
+ ${user.home}/keystores/client-keystore.jks
+
+
+ ssl.client.keystore.password
+ clientfoo
+
+
+
+
+ ssl.client.truststore.type
+ jks
+
+
+ ssl.client.truststore.location
+ ${user.home}/keystores/truststore.jks
+
+
+ ssl.client.truststore.password
+ clientserverbar
+
+
+ ssl.client.truststore.reload.interval
+ 10000
+
+
+------
+
+* Activating Encrypted Shuffle
+
+ When you have made the above configuration changes, activate Encrypted
+ Shuffle by re-starting all NodeManagers.
+
+ <> Using encrypted shuffle will incur in a significant
+ performance impact. Users should profile this and potentially reserve
+ 1 or more cores for encrypted shuffle.
+
+* {ClientCertificates} Client Certificates
+
+ Using Client Certificates does not fully ensure that the client is a
+ reducer task for the job. Currently, Client Certificates (their private key)
+ keystore files must be readable by all users submitting jobs to the cluster.
+ This means that a rogue job could read such those keystore files and use
+ the client certificates in them to establish a secure connection with a
+ Shuffle server. However, unless the rogue job has a proper JobToken, it won't
+ be able to retrieve shuffle data from the Shuffle server. A job, using its
+ own JobToken, can only retrieve shuffle data that belongs to itself.
+
+* Reloading Truststores
+
+ By default the truststores will reload their configuration every 10 seconds.
+ If a new truststore file is copied over the old one, it will be re-read,
+ and its certificates will replace the old ones. This mechanism is useful for
+ adding or removing nodes from the cluster, or for adding or removing trusted
+ clients. In these cases, the client or NodeManager certificate is added to
+ (or removed from) all the truststore files in the system, and the new
+ configuration will be picked up without you having to restart the NodeManager
+ daemons.
+
+* Debugging
+
+ <> Enable debugging only for troubleshooting, and then only for jobs
+ running on small amounts of data. It is very verbose and slows down jobs by
+ several orders of magnitude. (You might need to increase mapred.task.timeout
+ to prevent jobs from failing because tasks run so slowly.)
+
+ To enable SSL debugging in the reducers, set <<<-Djavax.net.debug=all>>> in
+ the <<>> property; for example:
+
+------
+
+ mapred.reduce.child.java.opts
+ -Xmx-200m -Djavax.net.debug=all
+
+------
+
+ You can do this on a per-job basis, or by means of a cluster-wide setting in
+ the <<>> file.
+
+ To set this property in NodeManager, set it in the <<>> file:
+
+------
+ YARN_NODEMANAGER_OPTS="-Djavax.net.debug=all $YARN_NODEMANAGER_OPTS"
+------
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/index.apt.vm b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/index.apt.vm
index dd086474982..badd9155509 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/index.apt.vm
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/index.apt.vm
@@ -51,3 +51,5 @@ MapReduce NextGen aka YARN aka MRv2
* {{{./CLIMiniCluster.html}CLI MiniCluster}}
+ * {{{./EncryptedShuffle.html}Encrypted Shuffle}}
+
diff --git a/hadoop-project/src/site/site.xml b/hadoop-project/src/site/site.xml
index 55333b1f883..f449b5d9b40 100644
--- a/hadoop-project/src/site/site.xml
+++ b/hadoop-project/src/site/site.xml
@@ -66,6 +66,7 @@
+
From 4d4560189adccb941a3dc5eee7add134adbf6519 Mon Sep 17 00:00:00 2001
From: Alejandro Abdelnur
Date: Thu, 26 Jul 2012 13:39:05 +0000
Subject: [PATCH 21/39] HDFS-3113. httpfs does not support delegation tokens.
(tucu)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1365988 13f79535-47bb-0310-9956-ffa450edef68
---
.../dev-support/findbugsExcludeFile.xml | 5 +
.../hadoop-hdfs-httpfs/pom.xml | 58 ++
.../fs/http/client/HttpFSFileSystem.java | 283 +++++-----
.../client/HttpFSKerberosAuthenticator.java | 226 ++++++++
...or.java => HttpFSPseudoAuthenticator.java} | 2 +-
.../hadoop/fs/http/client/HttpFSUtils.java | 148 +++++
...r.java => HttpFSAuthenticationFilter.java} | 9 +-
.../HttpFSKerberosAuthenticationHandler.java | 255 +++++++++
.../fs/http/server/HttpFSServerWebApp.java | 2 +-
.../hadoop/lib/server/ServerException.java | 6 +-
.../service/DelegationTokenIdentifier.java | 57 ++
.../lib/service/DelegationTokenManager.java | 76 +++
.../DelegationTokenManagerException.java | 49 ++
.../DelegationTokenManagerService.java | 231 ++++++++
.../hadoop/lib/servlet/ServerWebApp.java | 65 +++
.../src/main/resources/httpfs-default.xml | 31 +-
.../src/main/webapp/WEB-INF/web.xml | 2 +-
.../fs/http/client/TestHttpFSFileSystem.java | 18 +-
.../client/TestHttpFSFileSystem.java.orig | 513 ++++++++++++++++++
.../fs/http/client/TestWebhdfsFileSystem.java | 16 +-
...berosAuthenticationHandlerForTesting.java} | 30 +-
...stHttpFSKerberosAuthenticationHandler.java | 310 +++++++++++
.../fs/http/server/TestHttpFSServer.java | 129 ++++-
.../fs/http/server/TestHttpFSServer.java.orig | 236 ++++++++
.../http/server/TestHttpFSWithKerberos.java | 291 ++++++++++
.../TestDelegationTokenManagerService.java | 83 +++
.../apache/hadoop/test/KerberosTestUtils.java | 138 +++++
.../org/apache/hadoop/test/TestDirHelper.java | 4 +-
.../apache/hadoop/test/TestJettyHelper.java | 25 +-
.../hadoop/test/TestJettyHelper.java.orig | 118 ++++
.../src/test/resources/krb5.conf | 28 +
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 +
32 files changed, 3252 insertions(+), 194 deletions(-)
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSKerberosAuthenticator.java
rename hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/{HttpPseudoAuthenticator.java => HttpFSPseudoAuthenticator.java} (95%)
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSUtils.java
rename hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/{AuthFilter.java => HttpFSAuthenticationFilter.java} (92%)
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSKerberosAuthenticationHandler.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenIdentifier.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenManager.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenManagerException.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/security/DelegationTokenManagerService.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestHttpFSFileSystem.java.orig
rename hadoop-hdfs-project/hadoop-hdfs-httpfs/src/{main/java/org/apache/hadoop/fs/http/client/HttpKerberosAuthenticator.java => test/java/org/apache/hadoop/fs/http/server/HttpFSKerberosAuthenticationHandlerForTesting.java} (55%)
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSKerberosAuthenticationHandler.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSServer.java.orig
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSWithKerberos.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/lib/service/security/TestDelegationTokenManagerService.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/test/KerberosTestUtils.java
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/test/TestJettyHelper.java.orig
create mode 100644 hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/resources/krb5.conf
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/dev-support/findbugsExcludeFile.xml b/hadoop-hdfs-project/hadoop-hdfs-httpfs/dev-support/findbugsExcludeFile.xml
index 94c1d76bf3e..b72f5bc26e2 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/dev-support/findbugsExcludeFile.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/dev-support/findbugsExcludeFile.xml
@@ -25,4 +25,9 @@
+
+
+
+
+
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/pom.xml b/hadoop-hdfs-project/hadoop-hdfs-httpfs/pom.xml
index b40044aff5c..a3838b8efa6 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/pom.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/pom.xml
@@ -43,6 +43,8 @@
${project.build.directory}/${project.artifactId}-${project.version}/share/hadoop/httpfs/tomcat
+ LOCALHOST
+ **/TestHttpFSWithKerberos.java
@@ -267,6 +269,22 @@
+
+
+ ${basedir}/src/test/resources
+ false
+
+ krb5.conf
+
+
+
+ ${basedir}/src/test/resources
+ true
+
+ krb5.conf
+
+
+
@@ -281,6 +299,16 @@
maven-surefire-plugin
1
+ 600
+
+ ${project.build.directory}/test-classes/krb5.conf
+ ${kerberos.realm}
+
+
+ **/${test.exclude}.java
+ ${test.exclude.pattern}
+ ${test.exclude.kerberos.test}
+
@@ -395,6 +423,36 @@
+
+ testKerberos
+
+ false
+
+
+ _
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ once
+ 600
+
+ ${project.build.directory}/test-classes/krb5.conf
+ ${kerberos.realm}
+ localhost
+
+
+ **/TestHttpFSWithKerberos.java
+
+
+
+
+
+
+
docs
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSFileSystem.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSFileSystem.java
index 9191129e110..a5d4a467ce9 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSFileSystem.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSFileSystem.java
@@ -19,6 +19,7 @@ package org.apache.hadoop.fs.http.client;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.ContentSummary;
+import org.apache.hadoop.fs.DelegationTokenRenewer;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileChecksum;
@@ -28,16 +29,18 @@ import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.PositionedReadable;
import org.apache.hadoop.fs.Seekable;
import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
import org.apache.hadoop.security.authentication.client.Authenticator;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.TokenIdentifier;
import org.apache.hadoop.util.Progressable;
import org.apache.hadoop.util.ReflectionUtils;
import org.apache.hadoop.util.StringUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
-import org.json.simple.parser.JSONParser;
-import org.json.simple.parser.ParseException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -47,30 +50,32 @@ import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
import java.io.OutputStream;
-import java.lang.reflect.Constructor;
import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
-import java.net.URLEncoder;
+import java.security.PrivilegedExceptionAction;
import java.text.MessageFormat;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.Callable;
/**
* HttpFSServer implementation of the FileSystemAccess FileSystem.
*
* This implementation allows a user to access HDFS over HTTP via a HttpFSServer server.
*/
-public class HttpFSFileSystem extends FileSystem {
+public class HttpFSFileSystem extends FileSystem
+ implements DelegationTokenRenewer.Renewable {
- public static final String SERVICE_NAME = "/webhdfs";
+ public static final String SERVICE_NAME = HttpFSUtils.SERVICE_NAME;
- public static final String SERVICE_VERSION = "/v1";
+ public static final String SERVICE_VERSION = HttpFSUtils.SERVICE_VERSION;
- public static final String SERVICE_PREFIX = SERVICE_NAME + SERVICE_VERSION;
+ public static final String SCHEME = "webhdfs";
public static final String OP_PARAM = "op";
public static final String DO_AS_PARAM = "doas";
@@ -84,7 +89,6 @@ public class HttpFSFileSystem extends FileSystem {
public static final String GROUP_PARAM = "group";
public static final String MODIFICATION_TIME_PARAM = "modificationtime";
public static final String ACCESS_TIME_PARAM = "accesstime";
- public static final String RENEWER_PARAM = "renewer";
public static final Short DEFAULT_PERMISSION = 0755;
@@ -144,9 +148,6 @@ public class HttpFSFileSystem extends FileSystem {
public static final String CONTENT_SUMMARY_SPACE_CONSUMED_JSON = "spaceConsumed";
public static final String CONTENT_SUMMARY_SPACE_QUOTA_JSON = "spaceQuota";
- public static final String DELEGATION_TOKEN_JSON = "Token";
- public static final String DELEGATION_TOKEN_URL_STRING_JSON = "urlString";
-
public static final String ERROR_JSON = "RemoteException";
public static final String ERROR_EXCEPTION_JSON = "exception";
public static final String ERROR_CLASSNAME_JSON = "javaClassName";
@@ -184,8 +185,31 @@ public class HttpFSFileSystem extends FileSystem {
private AuthenticatedURL.Token authToken = new AuthenticatedURL.Token();
private URI uri;
+ private InetSocketAddress httpFSAddr;
private Path workingDir;
+ private UserGroupInformation realUser;
private String doAs;
+ private Token> delegationToken;
+
+ //This method enables handling UGI doAs with SPNEGO, we have to
+ //fallback to the realuser who logged in with Kerberos credentials
+ private T doAsRealUserIfNecessary(final Callable callable)
+ throws IOException {
+ try {
+ if (realUser.getShortUserName().equals(doAs)) {
+ return callable.call();
+ } else {
+ return realUser.doAs(new PrivilegedExceptionAction() {
+ @Override
+ public T run() throws Exception {
+ return callable.call();
+ }
+ });
+ }
+ } catch (Exception ex) {
+ throw new IOException(ex.toString(), ex);
+ }
+ }
/**
* Convenience method that creates a HttpURLConnection
for the
@@ -204,25 +228,23 @@ public class HttpFSFileSystem extends FileSystem {
*
* @throws IOException thrown if an IO error occurrs.
*/
- private HttpURLConnection getConnection(String method, Map params,
- Path path, boolean makeQualified) throws IOException {
- params.put(DO_AS_PARAM, doAs);
+ private HttpURLConnection getConnection(final String method,
+ Map params, Path path, boolean makeQualified)
+ throws IOException {
+ if (!realUser.getShortUserName().equals(doAs)) {
+ params.put(DO_AS_PARAM, doAs);
+ }
+ HttpFSKerberosAuthenticator.injectDelegationToken(params, delegationToken);
if (makeQualified) {
path = makeQualified(path);
}
- URI uri = path.toUri();
- StringBuilder sb = new StringBuilder();
- sb.append(uri.getScheme()).append("://").append(uri.getAuthority()).
- append(SERVICE_PREFIX).append(uri.getPath());
-
- String separator = "?";
- for (Map.Entry entry : params.entrySet()) {
- sb.append(separator).append(entry.getKey()).append("=").
- append(URLEncoder.encode(entry.getValue(), "UTF8"));
- separator = "&";
- }
- URL url = new URL(sb.toString());
- return getConnection(url, method);
+ final URL url = HttpFSUtils.createHttpURL(path, params);
+ return doAsRealUserIfNecessary(new Callable() {
+ @Override
+ public HttpURLConnection call() throws Exception {
+ return getConnection(url, method);
+ }
+ });
}
/**
@@ -240,7 +262,8 @@ public class HttpFSFileSystem extends FileSystem {
*/
private HttpURLConnection getConnection(URL url, String method) throws IOException {
Class extends Authenticator> klass =
- getConf().getClass("httpfs.authenticator.class", HttpKerberosAuthenticator.class, Authenticator.class);
+ getConf().getClass("httpfs.authenticator.class",
+ HttpFSKerberosAuthenticator.class, Authenticator.class);
Authenticator authenticator = ReflectionUtils.newInstance(klass, getConf());
try {
HttpURLConnection conn = new AuthenticatedURL(authenticator).openConnection(url, authToken);
@@ -254,63 +277,6 @@ public class HttpFSFileSystem extends FileSystem {
}
}
- /**
- * Convenience method that JSON Parses the InputStream
of a HttpURLConnection
.
- *
- * @param conn the HttpURLConnection
.
- *
- * @return the parsed JSON object.
- *
- * @throws IOException thrown if the InputStream
could not be JSON parsed.
- */
- private static Object jsonParse(HttpURLConnection conn) throws IOException {
- try {
- JSONParser parser = new JSONParser();
- return parser.parse(new InputStreamReader(conn.getInputStream()));
- } catch (ParseException ex) {
- throw new IOException("JSON parser error, " + ex.getMessage(), ex);
- }
- }
-
- /**
- * Validates the status of an HttpURLConnection
against an expected HTTP
- * status code. If the current status code is not the expected one it throws an exception
- * with a detail message using Server side error messages if available.
- *
- * @param conn the HttpURLConnection
.
- * @param expected the expected HTTP status code.
- *
- * @throws IOException thrown if the current status code does not match the expected one.
- */
- private static void validateResponse(HttpURLConnection conn, int expected) throws IOException {
- int status = conn.getResponseCode();
- if (status != expected) {
- try {
- JSONObject json = (JSONObject) jsonParse(conn);
- json = (JSONObject) json.get(ERROR_JSON);
- String message = (String) json.get(ERROR_MESSAGE_JSON);
- String exception = (String) json.get(ERROR_EXCEPTION_JSON);
- String className = (String) json.get(ERROR_CLASSNAME_JSON);
-
- try {
- ClassLoader cl = HttpFSFileSystem.class.getClassLoader();
- Class klass = cl.loadClass(className);
- Constructor constr = klass.getConstructor(String.class);
- throw (IOException) constr.newInstance(message);
- } catch (IOException ex) {
- throw ex;
- } catch (Exception ex) {
- throw new IOException(MessageFormat.format("{0} - {1}", exception, message));
- }
- } catch (IOException ex) {
- if (ex.getCause() instanceof IOException) {
- throw (IOException) ex.getCause();
- }
- throw new IOException(MessageFormat.format("HTTP status [{0}], {1}", status, conn.getResponseMessage()));
- }
- }
- }
-
/**
* Called after a new FileSystem instance is constructed.
*
@@ -320,15 +286,28 @@ public class HttpFSFileSystem extends FileSystem {
@Override
public void initialize(URI name, Configuration conf) throws IOException {
UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
- doAs = ugi.getUserName();
+
+ //the real use is the one that has the Kerberos credentials needed for
+ //SPNEGO to work
+ realUser = ugi.getRealUser();
+ if (realUser == null) {
+ realUser = UserGroupInformation.getLoginUser();
+ }
+ doAs = ugi.getShortUserName();
super.initialize(name, conf);
try {
- uri = new URI(name.getScheme() + "://" + name.getHost() + ":" + name.getPort());
+ uri = new URI(name.getScheme() + "://" + name.getAuthority());
+ httpFSAddr = NetUtils.createSocketAddr(getCanonicalUri().toString());
} catch (URISyntaxException ex) {
throw new IOException(ex);
}
}
+ @Override
+ public String getScheme() {
+ return SCHEME;
+ }
+
/**
* Returns a URI whose scheme and authority identify this FileSystem.
*
@@ -339,6 +318,16 @@ public class HttpFSFileSystem extends FileSystem {
return uri;
}
+ /**
+ * Get the default port for this file system.
+ * @return the default port or 0 if there isn't one
+ */
+ @Override
+ protected int getDefaultPort() {
+ return getConf().getInt(DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_KEY,
+ DFSConfigKeys.DFS_NAMENODE_HTTP_PORT_DEFAULT);
+ }
+
/**
* HttpFSServer subclass of the FSDataInputStream
.
*
@@ -397,7 +386,7 @@ public class HttpFSFileSystem extends FileSystem {
params.put(OP_PARAM, Operation.OPEN.toString());
HttpURLConnection conn = getConnection(Operation.OPEN.getMethod(), params,
f, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
return new FSDataInputStream(
new HttpFSDataInputStream(conn.getInputStream(), bufferSize));
}
@@ -424,7 +413,7 @@ public class HttpFSFileSystem extends FileSystem {
try {
super.close();
} finally {
- validateResponse(conn, closeStatus);
+ HttpFSUtils.validateResponse(conn, closeStatus);
}
}
@@ -460,11 +449,11 @@ public class HttpFSFileSystem extends FileSystem {
OutputStream os = new BufferedOutputStream(conn.getOutputStream(), bufferSize);
return new HttpFSDataOutputStream(conn, os, expectedStatus, statistics);
} catch (IOException ex) {
- validateResponse(conn, expectedStatus);
+ HttpFSUtils.validateResponse(conn, expectedStatus);
throw ex;
}
} else {
- validateResponse(conn, HTTP_TEMPORARY_REDIRECT);
+ HttpFSUtils.validateResponse(conn, HTTP_TEMPORARY_REDIRECT);
throw new IOException("Missing HTTP 'Location' header for [" + conn.getURL() + "]");
}
} else {
@@ -476,7 +465,7 @@ public class HttpFSFileSystem extends FileSystem {
if (exceptionAlreadyHandled) {
throw ex;
} else {
- validateResponse(conn, HTTP_TEMPORARY_REDIRECT);
+ HttpFSUtils.validateResponse(conn, HTTP_TEMPORARY_REDIRECT);
throw ex;
}
}
@@ -548,8 +537,8 @@ public class HttpFSFileSystem extends FileSystem {
params.put(DESTINATION_PARAM, dst.toString());
HttpURLConnection conn = getConnection(Operation.RENAME.getMethod(),
params, src, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- JSONObject json = (JSONObject) jsonParse(conn);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
return (Boolean) json.get(RENAME_JSON);
}
@@ -584,8 +573,8 @@ public class HttpFSFileSystem extends FileSystem {
params.put(RECURSIVE_PARAM, Boolean.toString(recursive));
HttpURLConnection conn = getConnection(Operation.DELETE.getMethod(),
params, f, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- JSONObject json = (JSONObject) jsonParse(conn);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
return (Boolean) json.get(DELETE_JSON);
}
@@ -605,8 +594,8 @@ public class HttpFSFileSystem extends FileSystem {
params.put(OP_PARAM, Operation.LISTSTATUS.toString());
HttpURLConnection conn = getConnection(Operation.LISTSTATUS.getMethod(),
params, f, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- JSONObject json = (JSONObject) jsonParse(conn);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
json = (JSONObject) json.get(FILE_STATUSES_JSON);
JSONArray jsonArray = (JSONArray) json.get(FILE_STATUS_JSON);
FileStatus[] array = new FileStatus[jsonArray.size()];
@@ -653,8 +642,8 @@ public class HttpFSFileSystem extends FileSystem {
params.put(PERMISSION_PARAM, permissionToString(permission));
HttpURLConnection conn = getConnection(Operation.MKDIRS.getMethod(),
params, f, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- JSONObject json = (JSONObject) jsonParse(conn);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
return (Boolean) json.get(MKDIRS_JSON);
}
@@ -674,8 +663,8 @@ public class HttpFSFileSystem extends FileSystem {
params.put(OP_PARAM, Operation.GETFILESTATUS.toString());
HttpURLConnection conn = getConnection(Operation.GETFILESTATUS.getMethod(),
params, f, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- JSONObject json = (JSONObject) jsonParse(conn);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
json = (JSONObject) json.get(FILE_STATUS_JSON);
f = makeQualified(f);
return createFileStatus(f, json);
@@ -693,8 +682,8 @@ public class HttpFSFileSystem extends FileSystem {
HttpURLConnection conn =
getConnection(Operation.GETHOMEDIRECTORY.getMethod(), params,
new Path(getUri().toString(), "/"), false);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- JSONObject json = (JSONObject) jsonParse(conn);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
return new Path((String) json.get(HOME_DIR_JSON));
} catch (IOException ex) {
throw new RuntimeException(ex);
@@ -718,7 +707,7 @@ public class HttpFSFileSystem extends FileSystem {
params.put(GROUP_PARAM, groupname);
HttpURLConnection conn = getConnection(Operation.SETOWNER.getMethod(),
params, p, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
}
/**
@@ -733,7 +722,7 @@ public class HttpFSFileSystem extends FileSystem {
params.put(OP_PARAM, Operation.SETPERMISSION.toString());
params.put(PERMISSION_PARAM, permissionToString(permission));
HttpURLConnection conn = getConnection(Operation.SETPERMISSION.getMethod(), params, p, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
}
/**
@@ -755,7 +744,7 @@ public class HttpFSFileSystem extends FileSystem {
params.put(ACCESS_TIME_PARAM, Long.toString(atime));
HttpURLConnection conn = getConnection(Operation.SETTIMES.getMethod(),
params, p, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
}
/**
@@ -777,19 +766,11 @@ public class HttpFSFileSystem extends FileSystem {
params.put(REPLICATION_PARAM, Short.toString(replication));
HttpURLConnection conn =
getConnection(Operation.SETREPLICATION.getMethod(), params, src, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- JSONObject json = (JSONObject) jsonParse(conn);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
return (Boolean) json.get(SET_REPLICATION_JSON);
}
- /**
- * Creates a FileStatus
object using a JSON file-status payload
- * received from a HttpFSServer server.
- *
- * @param json a JSON file-status payload received from a HttpFSServer server
- *
- * @return the corresponding FileStatus
- */
private FileStatus createFileStatus(Path parent, JSONObject json) {
String pathSuffix = (String) json.get(PATH_SUFFIX_JSON);
Path path = (pathSuffix.equals("")) ? parent : new Path(parent, pathSuffix);
@@ -828,9 +809,9 @@ public class HttpFSFileSystem extends FileSystem {
params.put(OP_PARAM, Operation.GETCONTENTSUMMARY.toString());
HttpURLConnection conn =
getConnection(Operation.GETCONTENTSUMMARY.getMethod(), params, f, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- JSONObject json =
- (JSONObject) ((JSONObject) jsonParse(conn)).get(CONTENT_SUMMARY_JSON);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) ((JSONObject)
+ HttpFSUtils.jsonParse(conn)).get(CONTENT_SUMMARY_JSON);
return new ContentSummary((Long) json.get(CONTENT_SUMMARY_LENGTH_JSON),
(Long) json.get(CONTENT_SUMMARY_FILE_COUNT_JSON),
(Long) json.get(CONTENT_SUMMARY_DIRECTORY_COUNT_JSON),
@@ -846,9 +827,9 @@ public class HttpFSFileSystem extends FileSystem {
params.put(OP_PARAM, Operation.GETFILECHECKSUM.toString());
HttpURLConnection conn =
getConnection(Operation.GETFILECHECKSUM.getMethod(), params, f, true);
- validateResponse(conn, HttpURLConnection.HTTP_OK);
- final JSONObject json =
- (JSONObject) ((JSONObject) jsonParse(conn)).get(FILE_CHECKSUM_JSON);
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ final JSONObject json = (JSONObject) ((JSONObject)
+ HttpFSUtils.jsonParse(conn)).get(FILE_CHECKSUM_JSON);
return new FileChecksum() {
@Override
public String getAlgorithmName() {
@@ -877,4 +858,56 @@ public class HttpFSFileSystem extends FileSystem {
};
}
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public Token> getDelegationToken(final String renewer)
+ throws IOException {
+ return doAsRealUserIfNecessary(new Callable>() {
+ @Override
+ public Token> call() throws Exception {
+ return HttpFSKerberosAuthenticator.
+ getDelegationToken(uri, httpFSAddr, authToken, renewer);
+ }
+ });
+ }
+
+
+ @Override
+ public List> getDelegationTokens(final String renewer)
+ throws IOException {
+ return doAsRealUserIfNecessary(new Callable>>() {
+ @Override
+ public List> call() throws Exception {
+ return HttpFSKerberosAuthenticator.
+ getDelegationTokens(uri, httpFSAddr, authToken, renewer);
+ }
+ });
+ }
+
+ public long renewDelegationToken(final Token> token) throws IOException {
+ return doAsRealUserIfNecessary(new Callable() {
+ @Override
+ public Long call() throws Exception {
+ return HttpFSKerberosAuthenticator.
+ renewDelegationToken(uri, authToken, token);
+ }
+ });
+ }
+
+ public void cancelDelegationToken(final Token> token) throws IOException {
+ HttpFSKerberosAuthenticator.
+ cancelDelegationToken(uri, authToken, token);
+ }
+
+ @Override
+ public Token> getRenewToken() {
+ return delegationToken;
+ }
+
+ @Override
+ public void setDelegationToken(Token token) {
+ delegationToken = token;
+ }
+
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSKerberosAuthenticator.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSKerberosAuthenticator.java
new file mode 100644
index 00000000000..f73ed7e3b00
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSKerberosAuthenticator.java
@@ -0,0 +1,226 @@
+/**
+ * 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.http.client;
+
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.security.SecurityUtil;
+import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
+import org.apache.hadoop.security.authentication.client.AuthenticationException;
+import org.apache.hadoop.security.authentication.client.Authenticator;
+import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A KerberosAuthenticator
subclass that fallback to
+ * {@link HttpFSPseudoAuthenticator}.
+ */
+public class HttpFSKerberosAuthenticator extends KerberosAuthenticator {
+
+ /**
+ * Returns the fallback authenticator if the server does not use
+ * Kerberos SPNEGO HTTP authentication.
+ *
+ * @return a {@link HttpFSPseudoAuthenticator} instance.
+ */
+ @Override
+ protected Authenticator getFallBackAuthenticator() {
+ return new HttpFSPseudoAuthenticator();
+ }
+
+ private static final String HTTP_GET = "GET";
+ private static final String HTTP_PUT = "PUT";
+
+ public static final String DELEGATION_PARAM = "delegation";
+ public static final String TOKEN_PARAM = "token";
+ public static final String RENEWER_PARAM = "renewer";
+ public static final String TOKEN_KIND = "HTTPFS_DELEGATION_TOKEN";
+ public static final String DELEGATION_TOKEN_JSON = "Token";
+ public static final String DELEGATION_TOKENS_JSON = "Tokens";
+ public static final String DELEGATION_TOKEN_URL_STRING_JSON = "urlString";
+ public static final String RENEW_DELEGATION_TOKEN_JSON = "long";
+
+ /**
+ * DelegationToken operations.
+ */
+ public static enum DelegationTokenOperation {
+ GETDELEGATIONTOKEN(HTTP_GET, true),
+ GETDELEGATIONTOKENS(HTTP_GET, true),
+ RENEWDELEGATIONTOKEN(HTTP_PUT, true),
+ CANCELDELEGATIONTOKEN(HTTP_PUT, false);
+
+ private String httpMethod;
+ private boolean requiresKerberosCredentials;
+
+ private DelegationTokenOperation(String httpMethod,
+ boolean requiresKerberosCredentials) {
+ this.httpMethod = httpMethod;
+ this.requiresKerberosCredentials = requiresKerberosCredentials;
+ }
+
+ public String getHttpMethod() {
+ return httpMethod;
+ }
+
+ public boolean requiresKerberosCredentials() {
+ return requiresKerberosCredentials;
+ }
+
+ }
+
+ public static void injectDelegationToken(Map params,
+ Token> dtToken)
+ throws IOException {
+ if (dtToken != null) {
+ params.put(DELEGATION_PARAM, dtToken.encodeToUrlString());
+ }
+ }
+
+ private boolean hasDelegationToken(URL url) {
+ return url.getQuery().contains(DELEGATION_PARAM + "=");
+ }
+
+ @Override
+ public void authenticate(URL url, AuthenticatedURL.Token token)
+ throws IOException, AuthenticationException {
+ if (!hasDelegationToken(url)) {
+ super.authenticate(url, token);
+ }
+ }
+
+ public static final String OP_PARAM = "op";
+
+ private static List> getDelegationTokens(URI fsURI,
+ InetSocketAddress httpFSAddr, DelegationTokenOperation op,
+ AuthenticatedURL.Token token, String renewer)
+ throws IOException {
+ Map params = new HashMap();
+ params.put(OP_PARAM, op.toString());
+ params.put(RENEWER_PARAM,renewer);
+ URL url = HttpFSUtils.createHttpURL(new Path(fsURI), params);
+ AuthenticatedURL aUrl =
+ new AuthenticatedURL(new HttpFSKerberosAuthenticator());
+ try {
+ HttpURLConnection conn = aUrl.openConnection(url, token);
+ conn.setRequestMethod(op.getHttpMethod());
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ List list = new ArrayList();
+ if (op == DelegationTokenOperation.GETDELEGATIONTOKEN) {
+ JSONObject json = (JSONObject) ((JSONObject)
+ HttpFSUtils.jsonParse(conn)).get(DELEGATION_TOKEN_JSON);
+ String tokenStr = (String)
+ json.get(DELEGATION_TOKEN_URL_STRING_JSON);
+ list.add(tokenStr);
+ }
+ else if (op == DelegationTokenOperation.GETDELEGATIONTOKENS) {
+ JSONObject json = (JSONObject) ((JSONObject)
+ HttpFSUtils.jsonParse(conn)).get(DELEGATION_TOKENS_JSON);
+ JSONArray array = (JSONArray) json.get(DELEGATION_TOKEN_JSON);
+ for (Object element : array) {
+ String tokenStr = (String)
+ ((Map) element).get(DELEGATION_TOKEN_URL_STRING_JSON);
+ list.add(tokenStr);
+ }
+
+ } else {
+ throw new IllegalArgumentException("Invalid operation: " +
+ op.toString());
+ }
+ List> dTokens = new ArrayList>();
+ for (String tokenStr : list) {
+ Token dToken =
+ new Token();
+ dToken.decodeFromUrlString(tokenStr);
+ dTokens.add(dToken);
+ SecurityUtil.setTokenService(dToken, httpFSAddr);
+ }
+ return dTokens;
+ } catch (AuthenticationException ex) {
+ throw new IOException(ex.toString(), ex);
+ }
+ }
+
+ public static List> getDelegationTokens(URI fsURI,
+ InetSocketAddress httpFSAddr, AuthenticatedURL.Token token,
+ String renewer) throws IOException {
+ return getDelegationTokens(fsURI, httpFSAddr,
+ DelegationTokenOperation.GETDELEGATIONTOKENS, token, renewer);
+ }
+
+ public static Token> getDelegationToken(URI fsURI,
+ InetSocketAddress httpFSAddr, AuthenticatedURL.Token token,
+ String renewer) throws IOException {
+ return getDelegationTokens(fsURI, httpFSAddr,
+ DelegationTokenOperation.GETDELEGATIONTOKENS, token, renewer).get(0);
+ }
+
+ public static long renewDelegationToken(URI fsURI,
+ AuthenticatedURL.Token token, Token> dToken) throws IOException {
+ Map params = new HashMap();
+ params.put(OP_PARAM,
+ DelegationTokenOperation.RENEWDELEGATIONTOKEN.toString());
+ params.put(TOKEN_PARAM, dToken.encodeToUrlString());
+ URL url = HttpFSUtils.createHttpURL(new Path(fsURI), params);
+ AuthenticatedURL aUrl =
+ new AuthenticatedURL(new HttpFSKerberosAuthenticator());
+ try {
+ HttpURLConnection conn = aUrl.openConnection(url, token);
+ conn.setRequestMethod(
+ DelegationTokenOperation.RENEWDELEGATIONTOKEN.getHttpMethod());
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) ((JSONObject)
+ HttpFSUtils.jsonParse(conn)).get(DELEGATION_TOKEN_JSON);
+ return (Long)(json.get(RENEW_DELEGATION_TOKEN_JSON));
+ } catch (AuthenticationException ex) {
+ throw new IOException(ex.toString(), ex);
+ }
+ }
+
+ public static void cancelDelegationToken(URI fsURI,
+ AuthenticatedURL.Token token, Token> dToken) throws IOException {
+ Map params = new HashMap();
+ params.put(OP_PARAM,
+ DelegationTokenOperation.CANCELDELEGATIONTOKEN.toString());
+ params.put(TOKEN_PARAM, dToken.encodeToUrlString());
+ URL url = HttpFSUtils.createHttpURL(new Path(fsURI), params);
+ AuthenticatedURL aUrl =
+ new AuthenticatedURL(new HttpFSKerberosAuthenticator());
+ try {
+ HttpURLConnection conn = aUrl.openConnection(url, token);
+ conn.setRequestMethod(
+ DelegationTokenOperation.CANCELDELEGATIONTOKEN.getHttpMethod());
+ HttpFSUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
+ } catch (AuthenticationException ex) {
+ throw new IOException(ex.toString(), ex);
+ }
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpPseudoAuthenticator.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSPseudoAuthenticator.java
similarity index 95%
rename from hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpPseudoAuthenticator.java
rename to hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSPseudoAuthenticator.java
index 9ac75a0aec9..927b1aa188d 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpPseudoAuthenticator.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSPseudoAuthenticator.java
@@ -27,7 +27,7 @@ import java.io.IOException;
* A PseudoAuthenticator
subclass that uses FileSystemAccess's
* UserGroupInformation
to obtain the client user name (the UGI's login user).
*/
-public class HttpPseudoAuthenticator extends PseudoAuthenticator {
+public class HttpFSPseudoAuthenticator extends PseudoAuthenticator {
/**
* Return the client user name.
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSUtils.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSUtils.java
new file mode 100644
index 00000000000..5a8f8c78fe6
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpFSUtils.java
@@ -0,0 +1,148 @@
+/**
+ * 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.http.client;
+
+import org.apache.hadoop.fs.Path;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Constructor;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.text.MessageFormat;
+import java.util.Map;
+
+/**
+ * Utility methods used by HttpFS classes.
+ */
+public class HttpFSUtils {
+
+ public static final String SERVICE_NAME = "/webhdfs";
+
+ public static final String SERVICE_VERSION = "/v1";
+
+ private static final String SERVICE_PATH = SERVICE_NAME + SERVICE_VERSION;
+
+ /**
+ * Convenience method that creates an HTTP URL
for the
+ * HttpFSServer file system operations.
+ *
+ *
+ * @param path the file path.
+ * @param params the query string parameters.
+ *
+ * @return a URL
for the HttpFSServer server,
+ *
+ * @throws IOException thrown if an IO error occurrs.
+ */
+ static URL createHttpURL(Path path, Map params)
+ throws IOException {
+ URI uri = path.toUri();
+ String realScheme;
+ if (uri.getScheme().equalsIgnoreCase(HttpFSFileSystem.SCHEME)) {
+ realScheme = "http";
+ } else {
+ throw new IllegalArgumentException(MessageFormat.format(
+ "Invalid scheme [{0}] it should be 'webhdfs'", uri));
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(realScheme).append("://").append(uri.getAuthority()).
+ append(SERVICE_PATH).append(uri.getPath());
+
+ String separator = "?";
+ for (Map.Entry entry : params.entrySet()) {
+ sb.append(separator).append(entry.getKey()).append("=").
+ append(URLEncoder.encode(entry.getValue(), "UTF8"));
+ separator = "&";
+ }
+ return new URL(sb.toString());
+ }
+
+ /**
+ * Validates the status of an HttpURLConnection
against an
+ * expected HTTP status code. If the current status code is not the expected
+ * one it throws an exception with a detail message using Server side error
+ * messages if available.
+ *
+ * @param conn the HttpURLConnection
.
+ * @param expected the expected HTTP status code.
+ *
+ * @throws IOException thrown if the current status code does not match the
+ * expected one.
+ */
+ @SuppressWarnings({"unchecked", "deprecation"})
+ static void validateResponse(HttpURLConnection conn, int expected)
+ throws IOException {
+ int status = conn.getResponseCode();
+ if (status != expected) {
+ try {
+ JSONObject json = (JSONObject) HttpFSUtils.jsonParse(conn);
+ json = (JSONObject) json.get(HttpFSFileSystem.ERROR_JSON);
+ String message = (String) json.get(HttpFSFileSystem.ERROR_MESSAGE_JSON);
+ String exception = (String)
+ json.get(HttpFSFileSystem.ERROR_EXCEPTION_JSON);
+ String className = (String)
+ json.get(HttpFSFileSystem.ERROR_CLASSNAME_JSON);
+
+ try {
+ ClassLoader cl = HttpFSFileSystem.class.getClassLoader();
+ Class klass = cl.loadClass(className);
+ Constructor constr = klass.getConstructor(String.class);
+ throw (IOException) constr.newInstance(message);
+ } catch (IOException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new IOException(MessageFormat.format("{0} - {1}", exception,
+ message));
+ }
+ } catch (IOException ex) {
+ if (ex.getCause() instanceof IOException) {
+ throw (IOException) ex.getCause();
+ }
+ throw new IOException(
+ MessageFormat.format("HTTP status [{0}], {1}",
+ status, conn.getResponseMessage()));
+ }
+ }
+ }
+
+ /**
+ * Convenience method that JSON Parses the InputStream
of a
+ * HttpURLConnection
.
+ *
+ * @param conn the HttpURLConnection
.
+ *
+ * @return the parsed JSON object.
+ *
+ * @throws IOException thrown if the InputStream
could not be
+ * JSON parsed.
+ */
+ static Object jsonParse(HttpURLConnection conn) throws IOException {
+ try {
+ JSONParser parser = new JSONParser();
+ return parser.parse(new InputStreamReader(conn.getInputStream()));
+ } catch (ParseException ex) {
+ throw new IOException("JSON parser error, " + ex.getMessage(), ex);
+ }
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/AuthFilter.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSAuthenticationFilter.java
similarity index 92%
rename from hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/AuthFilter.java
rename to hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSAuthenticationFilter.java
index ab778f6c692..4bcfa8447bf 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/AuthFilter.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSAuthenticationFilter.java
@@ -19,7 +19,6 @@ package org.apache.hadoop.fs.http.server;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
-
import javax.servlet.FilterConfig;
import java.io.FileReader;
import java.io.IOException;
@@ -31,7 +30,7 @@ import java.util.Properties;
* Subclass of hadoop-auth AuthenticationFilter
that obtains its configuration
* from HttpFSServer's server configuration.
*/
-public class AuthFilter extends AuthenticationFilter {
+public class HttpFSAuthenticationFilter extends AuthenticationFilter {
private static final String CONF_PREFIX = "httpfs.authentication.";
private static final String SIGNATURE_SECRET_FILE = SIGNATURE_SECRET + ".file";
@@ -63,6 +62,11 @@ public class AuthFilter extends AuthenticationFilter {
}
}
+ if (props.getProperty(AUTH_TYPE).equals("kerberos")) {
+ props.setProperty(AUTH_TYPE,
+ HttpFSKerberosAuthenticationHandler.class.getName());
+ }
+
String signatureSecretFile = props.getProperty(SIGNATURE_SECRET_FILE, null);
if (signatureSecretFile == null) {
throw new RuntimeException("Undefined property: " + SIGNATURE_SECRET_FILE);
@@ -84,5 +88,4 @@ public class AuthFilter extends AuthenticationFilter {
return props;
}
-
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSKerberosAuthenticationHandler.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSKerberosAuthenticationHandler.java
new file mode 100644
index 00000000000..f8602083e16
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSKerberosAuthenticationHandler.java
@@ -0,0 +1,255 @@
+/**
+ * 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.http.server;
+
+import org.apache.hadoop.fs.http.client.HttpFSFileSystem;
+import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator;
+import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator.DelegationTokenOperation;
+import org.apache.hadoop.lib.service.DelegationTokenIdentifier;
+import org.apache.hadoop.lib.service.DelegationTokenManager;
+import org.apache.hadoop.lib.service.DelegationTokenManagerException;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authentication.client.AuthenticationException;
+import org.apache.hadoop.security.authentication.server.AuthenticationToken;
+import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
+import org.apache.hadoop.security.token.Token;
+import org.json.simple.JSONObject;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Server side AuthenticationHandler
that authenticates requests
+ * using the incoming delegation token as a 'delegation' query string parameter.
+ *
+ * If not delegation token is present in the request it delegates to the
+ * {@link KerberosAuthenticationHandler}
+ */
+public class HttpFSKerberosAuthenticationHandler
+ extends KerberosAuthenticationHandler {
+
+ static final Set DELEGATION_TOKEN_OPS =
+ new HashSet();
+
+ static {
+ DELEGATION_TOKEN_OPS.add(
+ DelegationTokenOperation.GETDELEGATIONTOKEN.toString());
+ DELEGATION_TOKEN_OPS.add(
+ DelegationTokenOperation.GETDELEGATIONTOKENS.toString());
+ DELEGATION_TOKEN_OPS.add(
+ DelegationTokenOperation.RENEWDELEGATIONTOKEN.toString());
+ DELEGATION_TOKEN_OPS.add(
+ DelegationTokenOperation.CANCELDELEGATIONTOKEN.toString());
+ }
+
+ public static final String TYPE = "kerberos-dt";
+
+ /**
+ * Returns authentication type of the handler.
+ *
+ * @return delegationtoken-kerberos
+ */
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ private static final String ENTER = System.getProperty("line.separator");
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean managementOperation(AuthenticationToken token,
+ HttpServletRequest request, HttpServletResponse response)
+ throws IOException, AuthenticationException {
+ boolean requestContinues = true;
+ String op = request.getParameter(HttpFSFileSystem.OP_PARAM);
+ op = (op != null) ? op.toUpperCase() : null;
+ if (DELEGATION_TOKEN_OPS.contains(op) &&
+ !request.getMethod().equals("OPTIONS")) {
+ DelegationTokenOperation dtOp =
+ DelegationTokenOperation.valueOf(op);
+ if (dtOp.getHttpMethod().equals(request.getMethod())) {
+ if (dtOp.requiresKerberosCredentials() && token == null) {
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
+ MessageFormat.format(
+ "Operation [{0}] requires SPNEGO authentication established",
+ dtOp));
+ requestContinues = false;
+ } else {
+ DelegationTokenManager tokenManager =
+ HttpFSServerWebApp.get().get(DelegationTokenManager.class);
+ try {
+ Map map = null;
+ switch (dtOp) {
+ case GETDELEGATIONTOKEN:
+ case GETDELEGATIONTOKENS:
+ String renewerParam =
+ request.getParameter(HttpFSKerberosAuthenticator.RENEWER_PARAM);
+ if (renewerParam == null) {
+ renewerParam = token.getUserName();
+ }
+ Token> dToken = tokenManager.createToken(
+ UserGroupInformation.getCurrentUser(), renewerParam);
+ if (dtOp == DelegationTokenOperation.GETDELEGATIONTOKEN) {
+ map = delegationTokenToJSON(dToken);
+ } else {
+ map = delegationTokensToJSON(Arrays.asList((Token)dToken));
+ }
+ break;
+ case RENEWDELEGATIONTOKEN:
+ case CANCELDELEGATIONTOKEN:
+ String tokenParam =
+ request.getParameter(HttpFSKerberosAuthenticator.TOKEN_PARAM);
+ if (tokenParam == null) {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+ MessageFormat.format(
+ "Operation [{0}] requires the parameter [{1}]",
+ dtOp, HttpFSKerberosAuthenticator.TOKEN_PARAM));
+ requestContinues = false;
+ } else {
+ if (dtOp == DelegationTokenOperation.CANCELDELEGATIONTOKEN) {
+ Token dt =
+ new Token();
+ dt.decodeFromUrlString(tokenParam);
+ tokenManager.cancelToken(dt,
+ UserGroupInformation.getCurrentUser().getUserName());
+ } else {
+ Token dt =
+ new Token();
+ dt.decodeFromUrlString(tokenParam);
+ long expirationTime =
+ tokenManager.renewToken(dt, token.getUserName());
+ map = new HashMap();
+ map.put("long", expirationTime);
+ }
+ }
+ break;
+ }
+ if (requestContinues) {
+ response.setStatus(HttpServletResponse.SC_OK);
+ if (map != null) {
+ response.setContentType(MediaType.APPLICATION_JSON);
+ Writer writer = response.getWriter();
+ JSONObject.writeJSONString(map, writer);
+ writer.write(ENTER);
+ writer.flush();
+
+ }
+ requestContinues = false;
+ }
+ } catch (DelegationTokenManagerException ex) {
+ throw new AuthenticationException(ex.toString(), ex);
+ }
+ }
+ } else {
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+ MessageFormat.format(
+ "Wrong HTTP method [{0}] for operation [{1}], it should be [{2}]",
+ request.getMethod(), dtOp, dtOp.getHttpMethod()));
+ requestContinues = false;
+ }
+ }
+ return requestContinues;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Map delegationTokenToJSON(Token token) throws IOException {
+ Map json = new LinkedHashMap();
+ json.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON,
+ token.encodeToUrlString());
+ Map response = new LinkedHashMap();
+ response.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON, json);
+ return response;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Map delegationTokensToJSON(List tokens)
+ throws IOException {
+ List list = new ArrayList();
+ for (Token token : tokens) {
+ Map map = new HashMap();
+ map.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON,
+ token.encodeToUrlString());
+ list.add(map);
+ }
+ Map map = new HashMap();
+ map.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON, list);
+ Map response = new LinkedHashMap();
+ response.put(HttpFSKerberosAuthenticator.DELEGATION_TOKENS_JSON, map);
+ return response;
+ }
+
+ /**
+ * Authenticates a request looking for the delegation
+ * query-string parameter and verifying it is a valid token. If there is not
+ * delegation
query-string parameter, it delegates the
+ * authentication to the {@link KerberosAuthenticationHandler} unless it is
+ * disabled.
+ *
+ * @param request the HTTP client request.
+ * @param response the HTTP client response.
+ *
+ * @return the authentication token for the authenticated request.
+ * @throws IOException thrown if an IO error occurred.
+ * @throws AuthenticationException thrown if the authentication failed.
+ */
+ @Override
+ public AuthenticationToken authenticate(HttpServletRequest request,
+ HttpServletResponse response)
+ throws IOException, AuthenticationException {
+ AuthenticationToken token;
+ String delegationParam =
+ request.getParameter(HttpFSKerberosAuthenticator.DELEGATION_PARAM);
+ if (delegationParam != null) {
+ try {
+ Token dt =
+ new Token();
+ dt.decodeFromUrlString(delegationParam);
+ DelegationTokenManager tokenManager =
+ HttpFSServerWebApp.get().get(DelegationTokenManager.class);
+ UserGroupInformation ugi = tokenManager.verifyToken(dt);
+ final String shortName = ugi.getShortUserName();
+
+ // creating a ephemeral token
+ token = new AuthenticationToken(shortName, ugi.getUserName(),
+ getType());
+ token.setExpires(0);
+ } catch (Throwable ex) {
+ throw new AuthenticationException("Could not verify DelegationToken, " +
+ ex.toString(), ex);
+ }
+ } else {
+ token = super.authenticate(request, response);
+ }
+ return token;
+ }
+
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSServerWebApp.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSServerWebApp.java
index fec8aa0805b..8a768e89042 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSServerWebApp.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/server/HttpFSServerWebApp.java
@@ -70,7 +70,7 @@ public class HttpFSServerWebApp extends ServerWebApp {
/**
* Constructor used for testing purposes.
*/
- protected HttpFSServerWebApp(String homeDir, String configDir, String logDir,
+ public HttpFSServerWebApp(String homeDir, String configDir, String logDir,
String tempDir, Configuration config) {
super(NAME, homeDir, configDir, logDir, tempDir, config);
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/server/ServerException.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/server/ServerException.java
index 2330dcb30cb..1b1476279fd 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/server/ServerException.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/server/ServerException.java
@@ -39,7 +39,11 @@ public class ServerException extends XException {
S08("Could not load service classes, {0}"),
S09("Could not set service [{0}] programmatically -server shutting down-, {1}"),
S10("Service [{0}] requires service [{1}]"),
- S11("Service [{0}] exception during status change to [{1}] -server shutting down-, {2}");
+ S11("Service [{0}] exception during status change to [{1}] -server shutting down-, {2}"),
+ S12("Could not start service [{0}], {1}"),
+ S13("Missing system property [{0}]"),
+ S14("Could not initialize server, {0}")
+ ;
private String msg;
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenIdentifier.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenIdentifier.java
new file mode 100644
index 00000000000..8d1703fd36a
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenIdentifier.java
@@ -0,0 +1,57 @@
+/**
+ * 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.lib.service;
+
+import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier;
+
+/**
+ * HttpFS DelegationTokenIdentifier
implementation.
+ */
+public class DelegationTokenIdentifier
+ extends AbstractDelegationTokenIdentifier {
+
+ public static final Text KIND_NAME =
+ new Text(HttpFSKerberosAuthenticator.TOKEN_KIND);
+
+ public DelegationTokenIdentifier() {
+ }
+
+ /**
+ * Create a new delegation token identifier
+ *
+ * @param owner the effective username of the token owner
+ * @param renewer the username of the renewer
+ * @param realUser the real username of the token owner
+ */
+ public DelegationTokenIdentifier(Text owner, Text renewer, Text realUser) {
+ super(owner, renewer, realUser);
+ }
+
+
+ /**
+ * Returns the kind, TOKEN_KIND
.
+ * @return returns TOKEN_KIND
.
+ */
+ @Override
+ public Text getKind() {
+ return KIND_NAME;
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenManager.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenManager.java
new file mode 100644
index 00000000000..4c679576b19
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenManager.java
@@ -0,0 +1,76 @@
+/**
+ * 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.lib.service;
+
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.Token;
+
+/**
+ * Service interface to manage HttpFS delegation tokens.
+ */
+public interface DelegationTokenManager {
+
+ /**
+ * Creates a delegation token.
+ *
+ * @param ugi UGI creating the token.
+ * @param renewer token renewer.
+ * @return new delegation token.
+ * @throws DelegationTokenManagerException thrown if the token could not be
+ * created.
+ */
+ public Token createToken(UserGroupInformation ugi,
+ String renewer)
+ throws DelegationTokenManagerException;
+
+ /**
+ * Renews a delegation token.
+ *
+ * @param token delegation token to renew.
+ * @param renewer token renewer.
+ * @return epoc expiration time.
+ * @throws DelegationTokenManagerException thrown if the token could not be
+ * renewed.
+ */
+ public long renewToken(Token token, String renewer)
+ throws DelegationTokenManagerException;
+
+ /**
+ * Cancels a delegation token.
+ *
+ * @param token delegation token to cancel.
+ * @param canceler token canceler.
+ * @throws DelegationTokenManagerException thrown if the token could not be
+ * canceled.
+ */
+ public void cancelToken(Token token,
+ String canceler)
+ throws DelegationTokenManagerException;
+
+ /**
+ * Verifies a delegation token.
+ *
+ * @param token delegation token to verify.
+ * @return the UGI for the token.
+ * @throws DelegationTokenManagerException thrown if the token could not be
+ * verified.
+ */
+ public UserGroupInformation verifyToken(Token token)
+ throws DelegationTokenManagerException;
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenManagerException.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenManagerException.java
new file mode 100644
index 00000000000..0939f217a83
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/DelegationTokenManagerException.java
@@ -0,0 +1,49 @@
+/**
+ * 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.lib.service;
+
+import org.apache.hadoop.lib.lang.XException;
+
+/**
+ * Exception thrown by the {@link DelegationTokenManager} service implementation.
+ */
+public class DelegationTokenManagerException extends XException {
+
+ public enum ERROR implements XException.ERROR {
+ DT01("Could not verify delegation token, {0}"),
+ DT02("Could not renew delegation token, {0}"),
+ DT03("Could not cancel delegation token, {0}"),
+ DT04("Could not create delegation token, {0}");
+
+ private String template;
+
+ ERROR(String template) {
+ this.template = template;
+ }
+
+ @Override
+ public String getTemplate() {
+ return template;
+ }
+ }
+
+ public DelegationTokenManagerException(ERROR error, Object... params) {
+ super(error, params);
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/security/DelegationTokenManagerService.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/security/DelegationTokenManagerService.java
new file mode 100644
index 00000000000..3acd1649486
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/service/security/DelegationTokenManagerService.java
@@ -0,0 +1,231 @@
+/**
+ * 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.lib.service.security;
+
+import org.apache.hadoop.fs.http.server.HttpFSServerWebApp;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.lib.server.BaseService;
+import org.apache.hadoop.lib.server.ServerException;
+import org.apache.hadoop.lib.server.ServiceException;
+import org.apache.hadoop.lib.service.DelegationTokenIdentifier;
+import org.apache.hadoop.lib.service.DelegationTokenManager;
+import org.apache.hadoop.lib.service.DelegationTokenManagerException;
+import org.apache.hadoop.security.SecurityUtil;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * DelegationTokenManager service implementation.
+ */
+public class DelegationTokenManagerService extends BaseService
+ implements DelegationTokenManager {
+
+ private static final String PREFIX = "delegation.token.manager";
+
+ private static final String UPDATE_INTERVAL = "update.interval";
+
+ private static final String MAX_LIFETIME = "max.lifetime";
+
+ private static final String RENEW_INTERVAL = "renew.interval";
+
+ private static final long HOUR = 60 * 60 * 1000;
+ private static final long DAY = 24 * HOUR;
+
+ DelegationTokenSecretManager secretManager = null;
+
+ public DelegationTokenManagerService() {
+ super(PREFIX);
+ }
+
+ /**
+ * Initializes the service.
+ *
+ * @throws ServiceException thrown if the service could not be initialized.
+ */
+ @Override
+ protected void init() throws ServiceException {
+
+ long updateInterval = getServiceConfig().getLong(UPDATE_INTERVAL, DAY);
+ long maxLifetime = getServiceConfig().getLong(MAX_LIFETIME, 7 * DAY);
+ long renewInterval = getServiceConfig().getLong(RENEW_INTERVAL, DAY);
+ secretManager = new DelegationTokenSecretManager(updateInterval,
+ maxLifetime,
+ renewInterval, HOUR);
+ try {
+ secretManager.startThreads();
+ } catch (IOException ex) {
+ throw new ServiceException(ServiceException.ERROR.S12,
+ DelegationTokenManager.class.getSimpleName(),
+ ex.toString(), ex);
+ }
+ }
+
+ /**
+ * Destroys the service.
+ */
+ @Override
+ public void destroy() {
+ secretManager.stopThreads();
+ super.destroy();
+ }
+
+ /**
+ * Returns the service interface.
+ *
+ * @return the service interface.
+ */
+ @Override
+ public Class getInterface() {
+ return DelegationTokenManager.class;
+ }
+
+ /**
+ * Creates a delegation token.
+ *
+ * @param ugi UGI creating the token.
+ * @param renewer token renewer.
+ * @return new delegation token.
+ * @throws DelegationTokenManagerException thrown if the token could not be
+ * created.
+ */
+ @Override
+ public Token createToken(UserGroupInformation ugi,
+ String renewer)
+ throws DelegationTokenManagerException {
+ renewer = (renewer == null) ? ugi.getShortUserName() : renewer;
+ String user = ugi.getUserName();
+ Text owner = new Text(user);
+ Text realUser = null;
+ if (ugi.getRealUser() != null) {
+ realUser = new Text(ugi.getRealUser().getUserName());
+ }
+ DelegationTokenIdentifier tokenIdentifier =
+ new DelegationTokenIdentifier(owner, new Text(renewer), realUser);
+ Token token =
+ new Token(tokenIdentifier, secretManager);
+ try {
+ SecurityUtil.setTokenService(token,
+ HttpFSServerWebApp.get().getAuthority());
+ } catch (ServerException ex) {
+ throw new DelegationTokenManagerException(
+ DelegationTokenManagerException.ERROR.DT04, ex.toString(), ex);
+ }
+ return token;
+ }
+
+ /**
+ * Renews a delegation token.
+ *
+ * @param token delegation token to renew.
+ * @param renewer token renewer.
+ * @return epoc expiration time.
+ * @throws DelegationTokenManagerException thrown if the token could not be
+ * renewed.
+ */
+ @Override
+ public long renewToken(Token token, String renewer)
+ throws DelegationTokenManagerException {
+ try {
+ return secretManager.renewToken(token, renewer);
+ } catch (IOException ex) {
+ throw new DelegationTokenManagerException(
+ DelegationTokenManagerException.ERROR.DT02, ex.toString(), ex);
+ }
+ }
+
+ /**
+ * Cancels a delegation token.
+ *
+ * @param token delegation token to cancel.
+ * @param canceler token canceler.
+ * @throws DelegationTokenManagerException thrown if the token could not be
+ * canceled.
+ */
+ @Override
+ public void cancelToken(Token token,
+ String canceler)
+ throws DelegationTokenManagerException {
+ try {
+ secretManager.cancelToken(token, canceler);
+ } catch (IOException ex) {
+ throw new DelegationTokenManagerException(
+ DelegationTokenManagerException.ERROR.DT03, ex.toString(), ex);
+ }
+ }
+
+ /**
+ * Verifies a delegation token.
+ *
+ * @param token delegation token to verify.
+ * @return the UGI for the token.
+ * @throws DelegationTokenManagerException thrown if the token could not be
+ * verified.
+ */
+ @Override
+ public UserGroupInformation verifyToken(Token token)
+ throws DelegationTokenManagerException {
+ ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
+ DataInputStream dis = new DataInputStream(buf);
+ DelegationTokenIdentifier id = new DelegationTokenIdentifier();
+ try {
+ id.readFields(dis);
+ dis.close();
+ secretManager.verifyToken(id, token.getPassword());
+ } catch (Exception ex) {
+ throw new DelegationTokenManagerException(
+ DelegationTokenManagerException.ERROR.DT01, ex.toString(), ex);
+ }
+ return id.getUser();
+ }
+
+ private static class DelegationTokenSecretManager
+ extends AbstractDelegationTokenSecretManager {
+
+ /**
+ * Create a secret manager
+ *
+ * @param delegationKeyUpdateInterval the number of seconds for rolling new
+ * secret keys.
+ * @param delegationTokenMaxLifetime the maximum lifetime of the delegation
+ * tokens
+ * @param delegationTokenRenewInterval how often the tokens must be renewed
+ * @param delegationTokenRemoverScanInterval how often the tokens are
+ * scanned
+ * for expired tokens
+ */
+ public DelegationTokenSecretManager(long delegationKeyUpdateInterval,
+ long delegationTokenMaxLifetime,
+ long delegationTokenRenewInterval,
+ long delegationTokenRemoverScanInterval) {
+ super(delegationKeyUpdateInterval, delegationTokenMaxLifetime,
+ delegationTokenRenewInterval, delegationTokenRemoverScanInterval);
+ }
+
+ @Override
+ public DelegationTokenIdentifier createIdentifier() {
+ return new DelegationTokenIdentifier();
+ }
+
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/servlet/ServerWebApp.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/servlet/ServerWebApp.java
index 77e31456f7b..277b17fed22 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/servlet/ServerWebApp.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/lib/servlet/ServerWebApp.java
@@ -18,12 +18,16 @@
package org.apache.hadoop.lib.servlet;
+import com.google.common.annotations.VisibleForTesting;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.lib.server.Server;
import org.apache.hadoop.lib.server.ServerException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
import java.text.MessageFormat;
/**
@@ -36,9 +40,13 @@ public abstract class ServerWebApp extends Server implements ServletContextListe
private static final String CONFIG_DIR = ".config.dir";
private static final String LOG_DIR = ".log.dir";
private static final String TEMP_DIR = ".temp.dir";
+ private static final String HTTP_HOSTNAME = ".http.hostname";
+ private static final String HTTP_PORT = ".http.port";
private static ThreadLocal HOME_DIR_TL = new ThreadLocal();
+ private InetSocketAddress authority;
+
/**
* Method for testing purposes.
*/
@@ -146,6 +154,38 @@ public abstract class ServerWebApp extends Server implements ServletContextListe
}
}
+ /**
+ * Resolves the host & port InetSocketAddress the web server is listening to.
+ *
+ * This implementation looks for the following 2 properties:
+ *
+ * - #SERVER_NAME#.http.hostname
+ * - #SERVER_NAME#.http.port
+ *
+ *
+ * @return the host & port InetSocketAddress the web server is listening to.
+ * @throws ServerException thrown if any of the above 2 properties is not defined.
+ */
+ protected InetSocketAddress resolveAuthority() throws ServerException {
+ String hostnameKey = getName() + HTTP_HOSTNAME;
+ String portKey = getName() + HTTP_PORT;
+ String host = System.getProperty(hostnameKey);
+ String port = System.getProperty(portKey);
+ if (host == null) {
+ throw new ServerException(ServerException.ERROR.S13, hostnameKey);
+ }
+ if (port == null) {
+ throw new ServerException(ServerException.ERROR.S13, portKey);
+ }
+ try {
+ InetAddress add = InetAddress.getByName(hostnameKey);
+ int portNum = Integer.parseInt(port);
+ return new InetSocketAddress(add, portNum);
+ } catch (UnknownHostException ex) {
+ throw new ServerException(ServerException.ERROR.S14, ex.toString(), ex);
+ }
+ }
+
/**
* Destroys the ServletContextListener
which destroys
* the Server.
@@ -156,4 +196,29 @@ public abstract class ServerWebApp extends Server implements ServletContextListe
destroy();
}
+ /**
+ * Returns the hostname:port InetSocketAddress the webserver is listening to.
+ *
+ * @return the hostname:port InetSocketAddress the webserver is listening to.
+ */
+ public InetSocketAddress getAuthority() throws ServerException {
+ synchronized (this) {
+ if (authority == null) {
+ authority = resolveAuthority();
+ }
+ }
+ return authority;
+ }
+
+ /**
+ * Sets an alternate hostname:port InetSocketAddress to use.
+ *
+ * For testing purposes.
+ *
+ * @param authority alterante authority.
+ */
+ @VisibleForTesting
+ public void setAuthority(InetSocketAddress authority) {
+ this.authority = authority;
+ }
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/resources/httpfs-default.xml b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/resources/httpfs-default.xml
index a51f8722708..fc4faf5563e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/resources/httpfs-default.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/resources/httpfs-default.xml
@@ -35,6 +35,7 @@
org.apache.hadoop.lib.service.scheduler.SchedulerService,
org.apache.hadoop.lib.service.security.GroupsService,
org.apache.hadoop.lib.service.security.ProxyUserService,
+ org.apache.hadoop.lib.service.security.DelegationTokenManagerService,
org.apache.hadoop.lib.service.hadoop.FileSystemAccessService
@@ -88,12 +89,12 @@
Defines the authentication mechanism used by httpfs for its HTTP clients.
- Valid values are 'simple' and 'kerberos'.
+ Valid values are 'simple' or 'kerberos'.
If using 'simple' HTTP clients must specify the username with the
'user.name' query string parameter.
- If using 'kerberos' HTTP clients must use HTTP SPNEGO.
+ If using 'kerberos' HTTP clients must use HTTP SPNEGO or delegation tokens.
@@ -153,6 +154,32 @@
+
+
+
+ httpfs.delegation.token.manager.update.interval
+ 86400
+
+ HttpFS delegation token update interval, default 1 day, in seconds.
+
+
+
+
+ httpfs.delegation.token.manager.max.lifetime
+ 604800
+
+ HttpFS delegation token maximum lifetime, default 7 days, in seconds
+
+
+
+
+ httpfs.delegation.token.manager.renewal.interval
+ 86400
+
+ HttpFS delegation token update interval, default 1 day, in seconds.
+
+
+
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/webapp/WEB-INF/web.xml b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/webapp/WEB-INF/web.xml
index 4d5e976fc5a..4c0b3aedb0e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/webapp/WEB-INF/web.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/webapp/WEB-INF/web.xml
@@ -47,7 +47,7 @@
authFilter
- org.apache.hadoop.fs.http.server.AuthFilter
+ org.apache.hadoop.fs.http.server.HttpFSAuthenticationFilter
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestHttpFSFileSystem.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestHttpFSFileSystem.java
index ebebd25003f..aaa7356aff5 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestHttpFSFileSystem.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestHttpFSFileSystem.java
@@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
+import java.net.URI;
import java.net.URL;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
@@ -100,16 +101,24 @@ public class TestHttpFSFileSystem extends HFSTestCase {
server.start();
}
+ protected Class getFileSystemClass() {
+ return HttpFSFileSystem.class;
+ }
+
protected FileSystem getHttpFileSystem() throws Exception {
Configuration conf = new Configuration();
- conf.set("fs.http.impl", HttpFSFileSystem.class.getName());
- return FileSystem.get(TestJettyHelper.getJettyURL().toURI(), conf);
+ conf.set("fs.webhdfs.impl", getFileSystemClass().getName());
+ URI uri = new URI("webhdfs://" +
+ TestJettyHelper.getJettyURL().toURI().getAuthority());
+ return FileSystem.get(uri, conf);
}
protected void testGet() throws Exception {
FileSystem fs = getHttpFileSystem();
Assert.assertNotNull(fs);
- Assert.assertEquals(fs.getUri(), TestJettyHelper.getJettyURL().toURI());
+ URI uri = new URI("webhdfs://" +
+ TestJettyHelper.getJettyURL().toURI().getAuthority());
+ Assert.assertEquals(fs.getUri(), uri);
fs.close();
}
@@ -474,8 +483,9 @@ public class TestHttpFSFileSystem extends HFSTestCase {
for (int i = 0; i < Operation.values().length; i++) {
ops[i] = new Object[]{Operation.values()[i]};
}
+ //To test one or a subset of operations do:
+ //return Arrays.asList(new Object[][]{ new Object[]{Operation.OPEN}});
return Arrays.asList(ops);
-// return Arrays.asList(new Object[][]{ new Object[]{Operation.CREATE}});
}
private Operation operation;
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestHttpFSFileSystem.java.orig b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestHttpFSFileSystem.java.orig
new file mode 100644
index 00000000000..ebebd25003f
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestHttpFSFileSystem.java.orig
@@ -0,0 +1,513 @@
+/**
+ * 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.http.client;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.net.URL;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
+import org.apache.hadoop.fs.ContentSummary;
+import org.apache.hadoop.fs.FileChecksum;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.http.server.HttpFSServerWebApp;
+import org.apache.hadoop.fs.permission.FsAction;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.test.HFSTestCase;
+import org.apache.hadoop.test.HadoopUsersConfTestHelper;
+import org.apache.hadoop.test.TestDir;
+import org.apache.hadoop.test.TestDirHelper;
+import org.apache.hadoop.test.TestHdfs;
+import org.apache.hadoop.test.TestHdfsHelper;
+import org.apache.hadoop.test.TestJetty;
+import org.apache.hadoop.test.TestJettyHelper;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+@RunWith(value = Parameterized.class)
+public class TestHttpFSFileSystem extends HFSTestCase {
+
+ private void createHttpFSServer() throws Exception {
+ File homeDir = TestDirHelper.getTestDir();
+ Assert.assertTrue(new File(homeDir, "conf").mkdir());
+ Assert.assertTrue(new File(homeDir, "log").mkdir());
+ Assert.assertTrue(new File(homeDir, "temp").mkdir());
+ HttpFSServerWebApp.setHomeDirForCurrentThread(homeDir.getAbsolutePath());
+
+ File secretFile = new File(new File(homeDir, "conf"), "secret");
+ Writer w = new FileWriter(secretFile);
+ w.write("secret");
+ w.close();
+
+ //HDFS configuration
+ String fsDefaultName = TestHdfsHelper.getHdfsConf().get(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY);
+ Configuration conf = new Configuration(false);
+ conf.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, fsDefaultName);
+ File hdfsSite = new File(new File(homeDir, "conf"), "hdfs-site.xml");
+ OutputStream os = new FileOutputStream(hdfsSite);
+ conf.writeXml(os);
+ os.close();
+
+ //HTTPFS configuration
+ conf = new Configuration(false);
+ conf.set("httpfs.proxyuser." + HadoopUsersConfTestHelper.getHadoopProxyUser() + ".groups",
+ HadoopUsersConfTestHelper.getHadoopProxyUserGroups());
+ conf.set("httpfs.proxyuser." + HadoopUsersConfTestHelper.getHadoopProxyUser() + ".hosts",
+ HadoopUsersConfTestHelper.getHadoopProxyUserHosts());
+ conf.set("httpfs.authentication.signature.secret.file", secretFile.getAbsolutePath());
+ File httpfsSite = new File(new File(homeDir, "conf"), "httpfs-site.xml");
+ os = new FileOutputStream(httpfsSite);
+ conf.writeXml(os);
+ os.close();
+
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ URL url = cl.getResource("webapp");
+ WebAppContext context = new WebAppContext(url.getPath(), "/webhdfs");
+ Server server = TestJettyHelper.getJettyServer();
+ server.addHandler(context);
+ server.start();
+ }
+
+ protected FileSystem getHttpFileSystem() throws Exception {
+ Configuration conf = new Configuration();
+ conf.set("fs.http.impl", HttpFSFileSystem.class.getName());
+ return FileSystem.get(TestJettyHelper.getJettyURL().toURI(), conf);
+ }
+
+ protected void testGet() throws Exception {
+ FileSystem fs = getHttpFileSystem();
+ Assert.assertNotNull(fs);
+ Assert.assertEquals(fs.getUri(), TestJettyHelper.getJettyURL().toURI());
+ fs.close();
+ }
+
+ private void testOpen() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ OutputStream os = fs.create(path);
+ os.write(1);
+ os.close();
+ fs.close();
+ fs = getHttpFileSystem();
+ InputStream is = fs.open(new Path(path.toUri().getPath()));
+ Assert.assertEquals(is.read(), 1);
+ is.close();
+ fs.close();
+ }
+
+ private void testCreate(Path path, boolean override) throws Exception {
+ FileSystem fs = getHttpFileSystem();
+ FsPermission permission = new FsPermission(FsAction.READ_WRITE, FsAction.NONE, FsAction.NONE);
+ OutputStream os = fs.create(new Path(path.toUri().getPath()), permission, override, 1024,
+ (short) 2, 100 * 1024 * 1024, null);
+ os.write(1);
+ os.close();
+ fs.close();
+
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ FileStatus status = fs.getFileStatus(path);
+ Assert.assertEquals(status.getReplication(), 2);
+ Assert.assertEquals(status.getBlockSize(), 100 * 1024 * 1024);
+ Assert.assertEquals(status.getPermission(), permission);
+ InputStream is = fs.open(path);
+ Assert.assertEquals(is.read(), 1);
+ is.close();
+ fs.close();
+ }
+
+ private void testCreate() throws Exception {
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ testCreate(path, false);
+ testCreate(path, true);
+ try {
+ testCreate(path, false);
+ Assert.fail();
+ } catch (IOException ex) {
+
+ } catch (Exception ex) {
+ Assert.fail();
+ }
+ }
+
+ private void testAppend() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ OutputStream os = fs.create(path);
+ os.write(1);
+ os.close();
+ fs.close();
+ fs = getHttpFileSystem();
+ os = fs.append(new Path(path.toUri().getPath()));
+ os.write(2);
+ os.close();
+ fs.close();
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ InputStream is = fs.open(path);
+ Assert.assertEquals(is.read(), 1);
+ Assert.assertEquals(is.read(), 2);
+ Assert.assertEquals(is.read(), -1);
+ is.close();
+ fs.close();
+ }
+
+ private void testRename() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo");
+ fs.mkdirs(path);
+ fs.close();
+ fs = getHttpFileSystem();
+ Path oldPath = new Path(path.toUri().getPath());
+ Path newPath = new Path(path.getParent(), "bar");
+ fs.rename(oldPath, newPath);
+ fs.close();
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Assert.assertFalse(fs.exists(oldPath));
+ Assert.assertTrue(fs.exists(newPath));
+ fs.close();
+ }
+
+ private void testDelete() throws Exception {
+ Path foo = new Path(TestHdfsHelper.getHdfsTestDir(), "foo");
+ Path bar = new Path(TestHdfsHelper.getHdfsTestDir(), "bar");
+ Path foe = new Path(TestHdfsHelper.getHdfsTestDir(), "foe");
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ fs.mkdirs(foo);
+ fs.mkdirs(new Path(bar, "a"));
+ fs.mkdirs(foe);
+
+ FileSystem hoopFs = getHttpFileSystem();
+ Assert.assertTrue(hoopFs.delete(new Path(foo.toUri().getPath()), false));
+ Assert.assertFalse(fs.exists(foo));
+ try {
+ hoopFs.delete(new Path(bar.toUri().getPath()), false);
+ Assert.fail();
+ } catch (IOException ex) {
+ } catch (Exception ex) {
+ Assert.fail();
+ }
+ Assert.assertTrue(fs.exists(bar));
+ Assert.assertTrue(hoopFs.delete(new Path(bar.toUri().getPath()), true));
+ Assert.assertFalse(fs.exists(bar));
+
+ Assert.assertTrue(fs.exists(foe));
+ Assert.assertTrue(hoopFs.delete(foe, true));
+ Assert.assertFalse(fs.exists(foe));
+
+ hoopFs.close();
+ fs.close();
+ }
+
+ private void testListStatus() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ OutputStream os = fs.create(path);
+ os.write(1);
+ os.close();
+ FileStatus status1 = fs.getFileStatus(path);
+ fs.close();
+
+ fs = getHttpFileSystem();
+ FileStatus status2 = fs.getFileStatus(new Path(path.toUri().getPath()));
+ fs.close();
+
+ Assert.assertEquals(status2.getPermission(), status1.getPermission());
+ Assert.assertEquals(status2.getPath().toUri().getPath(), status1.getPath().toUri().getPath());
+ Assert.assertEquals(status2.getReplication(), status1.getReplication());
+ Assert.assertEquals(status2.getBlockSize(), status1.getBlockSize());
+ Assert.assertEquals(status2.getAccessTime(), status1.getAccessTime());
+ Assert.assertEquals(status2.getModificationTime(), status1.getModificationTime());
+ Assert.assertEquals(status2.getOwner(), status1.getOwner());
+ Assert.assertEquals(status2.getGroup(), status1.getGroup());
+ Assert.assertEquals(status2.getLen(), status1.getLen());
+
+ FileStatus[] stati = fs.listStatus(path.getParent());
+ Assert.assertEquals(stati.length, 1);
+ Assert.assertEquals(stati[0].getPath().getName(), path.getName());
+ }
+
+ private void testWorkingdirectory() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path workingDir = fs.getWorkingDirectory();
+ fs.close();
+
+ fs = getHttpFileSystem();
+ Path hoopWorkingDir = fs.getWorkingDirectory();
+ fs.close();
+ Assert.assertEquals(hoopWorkingDir.toUri().getPath(), workingDir.toUri().getPath());
+
+ fs = getHttpFileSystem();
+ fs.setWorkingDirectory(new Path("/tmp"));
+ workingDir = fs.getWorkingDirectory();
+ fs.close();
+ Assert.assertEquals(workingDir.toUri().getPath(), new Path("/tmp").toUri().getPath());
+ }
+
+ private void testMkdirs() throws Exception {
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo");
+ FileSystem fs = getHttpFileSystem();
+ fs.mkdirs(path);
+ fs.close();
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Assert.assertTrue(fs.exists(path));
+ fs.close();
+ }
+
+ private void testSetTimes() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ OutputStream os = fs.create(path);
+ os.write(1);
+ os.close();
+ FileStatus status1 = fs.getFileStatus(path);
+ fs.close();
+ long at = status1.getAccessTime();
+ long mt = status1.getModificationTime();
+
+ fs = getHttpFileSystem();
+ fs.setTimes(path, mt + 10, at + 20);
+ fs.close();
+
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ status1 = fs.getFileStatus(path);
+ fs.close();
+ long atNew = status1.getAccessTime();
+ long mtNew = status1.getModificationTime();
+ Assert.assertEquals(mtNew, mt + 10);
+ Assert.assertEquals(atNew, at + 20);
+ }
+
+ private void testSetPermission() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foodir");
+ fs.mkdirs(path);
+
+ fs = getHttpFileSystem();
+ FsPermission permission1 = new FsPermission(FsAction.READ_WRITE, FsAction.NONE, FsAction.NONE);
+ fs.setPermission(path, permission1);
+ fs.close();
+
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ FileStatus status1 = fs.getFileStatus(path);
+ fs.close();
+ FsPermission permission2 = status1.getPermission();
+ Assert.assertEquals(permission2, permission1);
+
+ //sticky bit
+ fs = getHttpFileSystem();
+ permission1 = new FsPermission(FsAction.READ_WRITE, FsAction.NONE, FsAction.NONE, true);
+ fs.setPermission(path, permission1);
+ fs.close();
+
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ status1 = fs.getFileStatus(path);
+ fs.close();
+ permission2 = status1.getPermission();
+ Assert.assertTrue(permission2.getStickyBit());
+ Assert.assertEquals(permission2, permission1);
+ }
+
+ private void testSetOwner() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ OutputStream os = fs.create(path);
+ os.write(1);
+ os.close();
+ fs.close();
+
+ fs = getHttpFileSystem();
+ String user = HadoopUsersConfTestHelper.getHadoopUsers()[1];
+ String group = HadoopUsersConfTestHelper.getHadoopUserGroups(user)[0];
+ fs.setOwner(path, user, group);
+ fs.close();
+
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ FileStatus status1 = fs.getFileStatus(path);
+ fs.close();
+ Assert.assertEquals(status1.getOwner(), user);
+ Assert.assertEquals(status1.getGroup(), group);
+ }
+
+ private void testSetReplication() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ OutputStream os = fs.create(path);
+ os.write(1);
+ os.close();
+ fs.close();
+ fs.setReplication(path, (short) 2);
+
+ fs = getHttpFileSystem();
+ fs.setReplication(path, (short) 1);
+ fs.close();
+
+ fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ FileStatus status1 = fs.getFileStatus(path);
+ fs.close();
+ Assert.assertEquals(status1.getReplication(), (short) 1);
+ }
+
+ private void testChecksum() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ OutputStream os = fs.create(path);
+ os.write(1);
+ os.close();
+ FileChecksum hdfsChecksum = fs.getFileChecksum(path);
+ fs.close();
+ fs = getHttpFileSystem();
+ FileChecksum httpChecksum = fs.getFileChecksum(path);
+ fs.close();
+ Assert.assertEquals(httpChecksum.getAlgorithmName(), hdfsChecksum.getAlgorithmName());
+ Assert.assertEquals(httpChecksum.getLength(), hdfsChecksum.getLength());
+ Assert.assertArrayEquals(httpChecksum.getBytes(), hdfsChecksum.getBytes());
+ }
+
+ private void testContentSummary() throws Exception {
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ Path path = new Path(TestHdfsHelper.getHdfsTestDir(), "foo.txt");
+ OutputStream os = fs.create(path);
+ os.write(1);
+ os.close();
+ ContentSummary hdfsContentSummary = fs.getContentSummary(path);
+ fs.close();
+ fs = getHttpFileSystem();
+ ContentSummary httpContentSummary = fs.getContentSummary(path);
+ fs.close();
+ Assert.assertEquals(httpContentSummary.getDirectoryCount(), hdfsContentSummary.getDirectoryCount());
+ Assert.assertEquals(httpContentSummary.getFileCount(), hdfsContentSummary.getFileCount());
+ Assert.assertEquals(httpContentSummary.getLength(), hdfsContentSummary.getLength());
+ Assert.assertEquals(httpContentSummary.getQuota(), hdfsContentSummary.getQuota());
+ Assert.assertEquals(httpContentSummary.getSpaceConsumed(), hdfsContentSummary.getSpaceConsumed());
+ Assert.assertEquals(httpContentSummary.getSpaceQuota(), hdfsContentSummary.getSpaceQuota());
+ }
+
+ protected enum Operation {
+ GET, OPEN, CREATE, APPEND, RENAME, DELETE, LIST_STATUS, WORKING_DIRECTORY, MKDIRS,
+ SET_TIMES, SET_PERMISSION, SET_OWNER, SET_REPLICATION, CHECKSUM, CONTENT_SUMMARY
+ }
+
+ private void operation(Operation op) throws Exception {
+ switch (op) {
+ case GET:
+ testGet();
+ break;
+ case OPEN:
+ testOpen();
+ break;
+ case CREATE:
+ testCreate();
+ break;
+ case APPEND:
+ testAppend();
+ break;
+ case RENAME:
+ testRename();
+ break;
+ case DELETE:
+ testDelete();
+ break;
+ case LIST_STATUS:
+ testListStatus();
+ break;
+ case WORKING_DIRECTORY:
+ testWorkingdirectory();
+ break;
+ case MKDIRS:
+ testMkdirs();
+ break;
+ case SET_TIMES:
+ testSetTimes();
+ break;
+ case SET_PERMISSION:
+ testSetPermission();
+ break;
+ case SET_OWNER:
+ testSetOwner();
+ break;
+ case SET_REPLICATION:
+ testSetReplication();
+ break;
+ case CHECKSUM:
+ testChecksum();
+ break;
+ case CONTENT_SUMMARY:
+ testContentSummary();
+ break;
+ }
+ }
+
+ @Parameterized.Parameters
+ public static Collection operations() {
+ Object[][] ops = new Object[Operation.values().length][];
+ for (int i = 0; i < Operation.values().length; i++) {
+ ops[i] = new Object[]{Operation.values()[i]};
+ }
+ return Arrays.asList(ops);
+// return Arrays.asList(new Object[][]{ new Object[]{Operation.CREATE}});
+ }
+
+ private Operation operation;
+
+ public TestHttpFSFileSystem(Operation operation) {
+ this.operation = operation;
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testOperation() throws Exception {
+ createHttpFSServer();
+ operation(operation);
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testOperationDoAs() throws Exception {
+ createHttpFSServer();
+ UserGroupInformation ugi = UserGroupInformation.createProxyUser(HadoopUsersConfTestHelper.getHadoopUsers()[0],
+ UserGroupInformation.getCurrentUser());
+ ugi.doAs(new PrivilegedExceptionAction() {
+ @Override
+ public Void run() throws Exception {
+ operation(operation);
+ return null;
+ }
+ });
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestWebhdfsFileSystem.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestWebhdfsFileSystem.java
index 95c005976c1..49aa81854a9 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestWebhdfsFileSystem.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/client/TestWebhdfsFileSystem.java
@@ -36,20 +36,8 @@ public class TestWebhdfsFileSystem extends TestHttpFSFileSystem {
}
@Override
- protected FileSystem getHttpFileSystem() throws Exception {
- Configuration conf = new Configuration();
- conf.set("fs.webhdfs.impl", WebHdfsFileSystem.class.getName());
- URI uri = new URI("webhdfs://" + TestJettyHelper.getJettyURL().toURI().getAuthority());
- return FileSystem.get(uri, conf);
- }
-
- @Override
- protected void testGet() throws Exception {
- FileSystem fs = getHttpFileSystem();
- Assert.assertNotNull(fs);
- URI uri = new URI("webhdfs://" + TestJettyHelper.getJettyURL().toURI().getAuthority());
- Assert.assertEquals(fs.getUri(), uri);
- fs.close();
+ protected Class getFileSystemClass() {
+ return WebHdfsFileSystem.class;
}
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpKerberosAuthenticator.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/HttpFSKerberosAuthenticationHandlerForTesting.java
similarity index 55%
rename from hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpKerberosAuthenticator.java
rename to hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/HttpFSKerberosAuthenticationHandlerForTesting.java
index 8f781bbb767..760cfd548a4 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/main/java/org/apache/hadoop/fs/http/client/HttpKerberosAuthenticator.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/HttpFSKerberosAuthenticationHandlerForTesting.java
@@ -15,27 +15,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.hadoop.fs.http.server;
-package org.apache.hadoop.fs.http.client;
+import javax.servlet.ServletException;
+import java.util.Properties;
+public class HttpFSKerberosAuthenticationHandlerForTesting
+ extends HttpFSKerberosAuthenticationHandler {
-import org.apache.hadoop.security.authentication.client.Authenticator;
-import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
-
-/**
- * A KerberosAuthenticator
subclass that fallback to
- * {@link HttpPseudoAuthenticator}.
- */
-public class HttpKerberosAuthenticator extends KerberosAuthenticator {
-
- /**
- * Returns the fallback authenticator if the server does not use
- * Kerberos SPNEGO HTTP authentication.
- *
- * @return a {@link HttpPseudoAuthenticator} instance.
- */
@Override
- protected Authenticator getFallBackAuthenticator() {
- return new HttpPseudoAuthenticator();
+ public void init(Properties config) throws ServletException {
+ //NOP overwrite to avoid Kerberos initialization
+ }
+
+ @Override
+ public void destroy() {
+ //NOP overwrite to avoid Kerberos initialization
}
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSKerberosAuthenticationHandler.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSKerberosAuthenticationHandler.java
new file mode 100644
index 00000000000..588488e830d
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSKerberosAuthenticationHandler.java
@@ -0,0 +1,310 @@
+/**
+ * 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.http.server;
+
+import junit.framework.Assert;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.http.client.HttpFSFileSystem;
+import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator;
+import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator.DelegationTokenOperation;
+import org.apache.hadoop.lib.service.DelegationTokenIdentifier;
+import org.apache.hadoop.lib.service.DelegationTokenManager;
+import org.apache.hadoop.lib.service.DelegationTokenManagerException;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authentication.client.AuthenticationException;
+import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
+import org.apache.hadoop.security.authentication.server.AuthenticationToken;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.test.HFSTestCase;
+import org.apache.hadoop.test.TestDir;
+import org.apache.hadoop.test.TestDirHelper;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+public class TestHttpFSKerberosAuthenticationHandler extends HFSTestCase {
+
+ @Test
+ @TestDir
+ public void testManagementOperations() throws Exception {
+ String dir = TestDirHelper.getTestDir().getAbsolutePath();
+
+ Configuration httpfsConf = new Configuration(false);
+ HttpFSServerWebApp server =
+ new HttpFSServerWebApp(dir, dir, dir, dir, httpfsConf);
+ server.setAuthority(new InetSocketAddress(InetAddress.getLocalHost(),
+ 14000));
+ AuthenticationHandler handler =
+ new HttpFSKerberosAuthenticationHandlerForTesting();
+ try {
+ server.init();
+ handler.init(null);
+
+ testNonManagementOperation(handler);
+ testManagementOperationErrors(handler);
+ testGetToken(handler, false, null);
+ testGetToken(handler, true, null);
+ testGetToken(handler, false, "foo");
+ testGetToken(handler, true, "foo");
+ testCancelToken(handler);
+ testRenewToken(handler);
+
+ } finally {
+ if (handler != null) {
+ handler.destroy();
+ }
+ server.destroy();
+ }
+ }
+
+ private void testNonManagementOperation(AuthenticationHandler handler)
+ throws Exception {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ Mockito.when(request.getParameter(HttpFSFileSystem.OP_PARAM)).
+ thenReturn(null);
+ Assert.assertTrue(handler.managementOperation(null, request, null));
+ Mockito.when(request.getParameter(HttpFSFileSystem.OP_PARAM)).
+ thenReturn(HttpFSFileSystem.Operation.CREATE.toString());
+ Assert.assertTrue(handler.managementOperation(null, request, null));
+ }
+
+ private void testManagementOperationErrors(AuthenticationHandler handler)
+ throws Exception {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ Mockito.when(request.getParameter(HttpFSFileSystem.OP_PARAM)).
+ thenReturn(DelegationTokenOperation.GETDELEGATIONTOKEN.toString());
+ Mockito.when(request.getMethod()).thenReturn("FOO");
+ Assert.assertFalse(handler.managementOperation(null, request, response));
+ Mockito.verify(response).sendError(
+ Mockito.eq(HttpServletResponse.SC_BAD_REQUEST),
+ Mockito.startsWith("Wrong HTTP method"));
+
+ Mockito.reset(response);
+ Mockito.when(request.getMethod()).
+ thenReturn(DelegationTokenOperation.GETDELEGATIONTOKEN.getHttpMethod());
+ Assert.assertFalse(handler.managementOperation(null, request, response));
+ Mockito.verify(response).sendError(
+ Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED),
+ Mockito.contains("requires SPNEGO"));
+ }
+
+ private void testGetToken(AuthenticationHandler handler, boolean tokens,
+ String renewer)
+ throws Exception {
+ DelegationTokenOperation op =
+ (tokens) ? DelegationTokenOperation.GETDELEGATIONTOKENS
+ : DelegationTokenOperation.GETDELEGATIONTOKEN;
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ Mockito.when(request.getParameter(HttpFSFileSystem.OP_PARAM)).
+ thenReturn(op.toString());
+ Mockito.when(request.getMethod()).
+ thenReturn(op.getHttpMethod());
+
+ AuthenticationToken token = Mockito.mock(AuthenticationToken.class);
+ Mockito.when(token.getUserName()).thenReturn("user");
+ Assert.assertFalse(handler.managementOperation(null, request, response));
+ Mockito.when(request.getParameter(HttpFSKerberosAuthenticator.RENEWER_PARAM)).
+ thenReturn(renewer);
+
+ Mockito.reset(response);
+ StringWriter writer = new StringWriter();
+ PrintWriter pwriter = new PrintWriter(writer);
+ Mockito.when(response.getWriter()).thenReturn(pwriter);
+ Assert.assertFalse(handler.managementOperation(token, request, response));
+ if (renewer == null) {
+ Mockito.verify(token).getUserName();
+ } else {
+ Mockito.verify(token, Mockito.never()).getUserName();
+ }
+ Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
+ Mockito.verify(response).setContentType(MediaType.APPLICATION_JSON);
+ pwriter.close();
+ String responseOutput = writer.toString();
+ String tokenLabel = (tokens)
+ ? HttpFSKerberosAuthenticator.DELEGATION_TOKENS_JSON
+ : HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON;
+ if (tokens) {
+ Assert.assertTrue(responseOutput.contains(tokenLabel));
+ } else {
+ Assert.assertTrue(responseOutput.contains(tokenLabel));
+ }
+ Assert.assertTrue(responseOutput.contains(
+ HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON));
+ JSONObject json = (JSONObject) new JSONParser().parse(responseOutput);
+ json = (JSONObject) json.get(tokenLabel);
+ String tokenStr;
+ if (tokens) {
+ json = (JSONObject) ((JSONArray)
+ json.get(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON)).get(0);
+ }
+ tokenStr = (String)
+ json.get(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON);
+ Token dt = new Token();
+ dt.decodeFromUrlString(tokenStr);
+ HttpFSServerWebApp.get().get(DelegationTokenManager.class).verifyToken(dt);
+ }
+
+ private void testCancelToken(AuthenticationHandler handler)
+ throws Exception {
+ DelegationTokenOperation op =
+ DelegationTokenOperation.CANCELDELEGATIONTOKEN;
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ Mockito.when(request.getParameter(HttpFSFileSystem.OP_PARAM)).
+ thenReturn(op.toString());
+ Mockito.when(request.getMethod()).
+ thenReturn(op.getHttpMethod());
+
+ Assert.assertFalse(handler.managementOperation(null, request, response));
+ Mockito.verify(response).sendError(
+ Mockito.eq(HttpServletResponse.SC_BAD_REQUEST),
+ Mockito.contains("requires the parameter [token]"));
+
+ Mockito.reset(response);
+ Token token =
+ HttpFSServerWebApp.get().get(DelegationTokenManager.class).createToken(
+ UserGroupInformation.getCurrentUser(), "foo");
+ Mockito.when(request.getParameter(HttpFSKerberosAuthenticator.TOKEN_PARAM)).
+ thenReturn(token.encodeToUrlString());
+ Assert.assertFalse(handler.managementOperation(null, request, response));
+ Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
+ try {
+ HttpFSServerWebApp.get().get(DelegationTokenManager.class).verifyToken(token);
+ Assert.fail();
+ }
+ catch (DelegationTokenManagerException ex) {
+ Assert.assertTrue(ex.toString().contains("DT01"));
+ }
+ }
+
+ private void testRenewToken(AuthenticationHandler handler)
+ throws Exception {
+ DelegationTokenOperation op =
+ DelegationTokenOperation.RENEWDELEGATIONTOKEN;
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ Mockito.when(request.getParameter(HttpFSFileSystem.OP_PARAM)).
+ thenReturn(op.toString());
+ Mockito.when(request.getMethod()).
+ thenReturn(op.getHttpMethod());
+
+ Assert.assertFalse(handler.managementOperation(null, request, response));
+ Mockito.verify(response).sendError(
+ Mockito.eq(HttpServletResponse.SC_UNAUTHORIZED),
+ Mockito.contains("equires SPNEGO authentication established"));
+
+ Mockito.reset(response);
+ AuthenticationToken token = Mockito.mock(AuthenticationToken.class);
+ Mockito.when(token.getUserName()).thenReturn("user");
+ Assert.assertFalse(handler.managementOperation(token, request, response));
+ Mockito.verify(response).sendError(
+ Mockito.eq(HttpServletResponse.SC_BAD_REQUEST),
+ Mockito.contains("requires the parameter [token]"));
+
+ Mockito.reset(response);
+ StringWriter writer = new StringWriter();
+ PrintWriter pwriter = new PrintWriter(writer);
+ Mockito.when(response.getWriter()).thenReturn(pwriter);
+ Token dToken =
+ HttpFSServerWebApp.get().get(DelegationTokenManager.class).createToken(
+ UserGroupInformation.getCurrentUser(), "user");
+ Mockito.when(request.getParameter(HttpFSKerberosAuthenticator.TOKEN_PARAM)).
+ thenReturn(dToken.encodeToUrlString());
+ Assert.assertFalse(handler.managementOperation(token, request, response));
+ Mockito.verify(response).setStatus(HttpServletResponse.SC_OK);
+ pwriter.close();
+ Assert.assertTrue(writer.toString().contains("long"));
+ HttpFSServerWebApp.get().get(DelegationTokenManager.class).verifyToken(dToken);
+ }
+
+ @Test
+ @TestDir
+ public void testAuthenticate() throws Exception {
+ String dir = TestDirHelper.getTestDir().getAbsolutePath();
+
+ Configuration httpfsConf = new Configuration(false);
+ HttpFSServerWebApp server =
+ new HttpFSServerWebApp(dir, dir, dir, dir, httpfsConf);
+ server.setAuthority(new InetSocketAddress(InetAddress.getLocalHost(),
+ 14000));
+ AuthenticationHandler handler =
+ new HttpFSKerberosAuthenticationHandlerForTesting();
+ try {
+ server.init();
+ handler.init(null);
+
+ testValidDelegationToken(handler);
+ testInvalidDelegationToken(handler);
+ } finally {
+ if (handler != null) {
+ handler.destroy();
+ }
+ server.destroy();
+ }
+ }
+
+ private void testValidDelegationToken(AuthenticationHandler handler)
+ throws Exception {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ Token dToken =
+ HttpFSServerWebApp.get().get(DelegationTokenManager.class).createToken(
+ UserGroupInformation.getCurrentUser(), "user");
+ Mockito.when(request.getParameter(HttpFSKerberosAuthenticator.DELEGATION_PARAM)).
+ thenReturn(dToken.encodeToUrlString());
+
+ AuthenticationToken token = handler.authenticate(request, response);
+ Assert.assertEquals(UserGroupInformation.getCurrentUser().getShortUserName(),
+ token.getUserName());
+ Assert.assertEquals(0, token.getExpires());
+ Assert.assertEquals(HttpFSKerberosAuthenticationHandler.TYPE,
+ token.getType());
+ Assert.assertTrue(token.isExpired());
+ }
+
+ private void testInvalidDelegationToken(AuthenticationHandler handler)
+ throws Exception {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ Mockito.when(request.getParameter(HttpFSKerberosAuthenticator.DELEGATION_PARAM)).
+ thenReturn("invalid");
+
+ try {
+ handler.authenticate(request, response);
+ Assert.fail();
+ } catch (AuthenticationException ex) {
+ //NOP
+ } catch (Exception ex) {
+ Assert.fail();
+ }
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSServer.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSServer.java
index 099eb4bf81b..910eeefb93e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSServer.java
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSServer.java
@@ -15,11 +15,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.hadoop.fs.http.server;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import org.junit.Assert;
import java.io.BufferedReader;
import java.io.File;
@@ -39,9 +37,13 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator;
import org.apache.hadoop.lib.server.Service;
import org.apache.hadoop.lib.server.ServiceException;
import org.apache.hadoop.lib.service.Groups;
+import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
+import org.apache.hadoop.security.authentication.server.AuthenticationToken;
+import org.apache.hadoop.security.authentication.util.Signer;
import org.apache.hadoop.test.HFSTestCase;
import org.apache.hadoop.test.HadoopUsersConfTestHelper;
import org.apache.hadoop.test.TestDir;
@@ -50,6 +52,8 @@ import org.apache.hadoop.test.TestHdfs;
import org.apache.hadoop.test.TestHdfsHelper;
import org.apache.hadoop.test.TestJetty;
import org.apache.hadoop.test.TestJettyHelper;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
import org.junit.Test;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.webapp.WebAppContext;
@@ -103,11 +107,13 @@ public class TestHttpFSServer extends HFSTestCase {
}
}
- private void createHttpFSServer() throws Exception {
+
+ private void createHttpFSServer(boolean addDelegationTokenAuthHandler)
+ throws Exception {
File homeDir = TestDirHelper.getTestDir();
- assertTrue(new File(homeDir, "conf").mkdir());
- assertTrue(new File(homeDir, "log").mkdir());
- assertTrue(new File(homeDir, "temp").mkdir());
+ Assert.assertTrue(new File(homeDir, "conf").mkdir());
+ Assert.assertTrue(new File(homeDir, "log").mkdir());
+ Assert.assertTrue(new File(homeDir, "temp").mkdir());
HttpFSServerWebApp.setHomeDirForCurrentThread(homeDir.getAbsolutePath());
File secretFile = new File(new File(homeDir, "conf"), "secret");
@@ -128,6 +134,10 @@ public class TestHttpFSServer extends HFSTestCase {
//HTTPFS configuration
conf = new Configuration(false);
+ if (addDelegationTokenAuthHandler) {
+ conf.set("httpfs.authentication.type",
+ HttpFSKerberosAuthenticationHandlerForTesting.class.getName());
+ }
conf.set("httpfs.services.ext", MockGroups.class.getName());
conf.set("httpfs.admin.group", HadoopUsersConfTestHelper.
getHadoopUserGroups(HadoopUsersConfTestHelper.getHadoopUsers()[0])[0]);
@@ -147,6 +157,9 @@ public class TestHttpFSServer extends HFSTestCase {
Server server = TestJettyHelper.getJettyServer();
server.addHandler(context);
server.start();
+ if (addDelegationTokenAuthHandler) {
+ HttpFSServerWebApp.get().setAuthority(TestJettyHelper.getAuthority());
+ }
}
@Test
@@ -154,28 +167,28 @@ public class TestHttpFSServer extends HFSTestCase {
@TestJetty
@TestHdfs
public void instrumentation() throws Exception {
- createHttpFSServer();
+ createHttpFSServer(false);
URL url = new URL(TestJettyHelper.getJettyURL(),
MessageFormat.format("/webhdfs/v1?user.name={0}&op=instrumentation", "nobody"));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_UNAUTHORIZED);
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_UNAUTHORIZED);
url = new URL(TestJettyHelper.getJettyURL(),
MessageFormat.format("/webhdfs/v1?user.name={0}&op=instrumentation",
HadoopUsersConfTestHelper.getHadoopUsers()[0]));
conn = (HttpURLConnection) url.openConnection();
- assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = reader.readLine();
reader.close();
- assertTrue(line.contains("\"counters\":{"));
+ Assert.assertTrue(line.contains("\"counters\":{"));
url = new URL(TestJettyHelper.getJettyURL(),
MessageFormat.format("/webhdfs/v1/foo?user.name={0}&op=instrumentation",
HadoopUsersConfTestHelper.getHadoopUsers()[0]));
conn = (HttpURLConnection) url.openConnection();
- assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_BAD_REQUEST);
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_BAD_REQUEST);
}
@Test
@@ -183,13 +196,13 @@ public class TestHttpFSServer extends HFSTestCase {
@TestJetty
@TestHdfs
public void testHdfsAccess() throws Exception {
- createHttpFSServer();
+ createHttpFSServer(false);
String user = HadoopUsersConfTestHelper.getHadoopUsers()[0];
URL url = new URL(TestJettyHelper.getJettyURL(),
MessageFormat.format("/webhdfs/v1/?user.name={0}&op=liststatus", user));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
reader.readLine();
reader.close();
@@ -200,7 +213,7 @@ public class TestHttpFSServer extends HFSTestCase {
@TestJetty
@TestHdfs
public void testGlobFilter() throws Exception {
- createHttpFSServer();
+ createHttpFSServer(false);
FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
fs.mkdirs(new Path("/tmp"));
@@ -210,7 +223,7 @@ public class TestHttpFSServer extends HFSTestCase {
URL url = new URL(TestJettyHelper.getJettyURL(),
MessageFormat.format("/webhdfs/v1/tmp?user.name={0}&op=liststatus&filter=f*", user));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
reader.readLine();
reader.close();
@@ -221,7 +234,7 @@ public class TestHttpFSServer extends HFSTestCase {
@TestJetty
@TestHdfs
public void testPutNoOperation() throws Exception {
- createHttpFSServer();
+ createHttpFSServer(false);
String user = HadoopUsersConfTestHelper.getHadoopUsers()[0];
URL url = new URL(TestJettyHelper.getJettyURL(),
@@ -230,7 +243,87 @@ public class TestHttpFSServer extends HFSTestCase {
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("PUT");
- assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_BAD_REQUEST);
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_BAD_REQUEST);
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testDelegationTokenOperations() throws Exception {
+ createHttpFSServer(true);
+
+ URL url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETHOMEDIRECTORY");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ Assert.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED,
+ conn.getResponseCode());
+
+
+ AuthenticationToken token =
+ new AuthenticationToken("u", "p",
+ HttpFSKerberosAuthenticationHandlerForTesting.TYPE);
+ token.setExpires(System.currentTimeMillis() + 100000000);
+ Signer signer = new Signer("secret".getBytes());
+ String tokenSigned = signer.sign(token.toString());
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETHOMEDIRECTORY");
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty("Cookie",
+ AuthenticatedURL.AUTH_COOKIE + "=" + tokenSigned);
+ Assert.assertEquals(HttpURLConnection.HTTP_OK,
+ conn.getResponseCode());
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETDELEGATIONTOKEN");
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty("Cookie",
+ AuthenticatedURL.AUTH_COOKIE + "=" + tokenSigned);
+ Assert.assertEquals(HttpURLConnection.HTTP_OK,
+ conn.getResponseCode());
+
+ JSONObject json = (JSONObject)
+ new JSONParser().parse(new InputStreamReader(conn.getInputStream()));
+ json = (JSONObject)
+ json.get(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON);
+ String tokenStr = (String)
+ json.get(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON);
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETHOMEDIRECTORY&delegation=" + tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ Assert.assertEquals(HttpURLConnection.HTTP_OK,
+ conn.getResponseCode());
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=RENEWDELEGATIONTOKEN&token=" + tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("PUT");
+ Assert.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED,
+ conn.getResponseCode());
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=RENEWDELEGATIONTOKEN&token=" + tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("PUT");
+ conn.setRequestProperty("Cookie",
+ AuthenticatedURL.AUTH_COOKIE + "=" + tokenSigned);
+ Assert.assertEquals(HttpURLConnection.HTTP_OK,
+ conn.getResponseCode());
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=CANCELDELEGATIONTOKEN&token=" + tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("PUT");
+ Assert.assertEquals(HttpURLConnection.HTTP_OK,
+ conn.getResponseCode());
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETHOMEDIRECTORY&delegation=" + tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ Assert.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED,
+ conn.getResponseCode());
}
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSServer.java.orig b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSServer.java.orig
new file mode 100644
index 00000000000..099eb4bf81b
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSServer.java.orig
@@ -0,0 +1,236 @@
+/**
+ * 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.http.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.lib.server.Service;
+import org.apache.hadoop.lib.server.ServiceException;
+import org.apache.hadoop.lib.service.Groups;
+import org.apache.hadoop.test.HFSTestCase;
+import org.apache.hadoop.test.HadoopUsersConfTestHelper;
+import org.apache.hadoop.test.TestDir;
+import org.apache.hadoop.test.TestDirHelper;
+import org.apache.hadoop.test.TestHdfs;
+import org.apache.hadoop.test.TestHdfsHelper;
+import org.apache.hadoop.test.TestJetty;
+import org.apache.hadoop.test.TestJettyHelper;
+import org.junit.Test;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+public class TestHttpFSServer extends HFSTestCase {
+
+ @Test
+ @TestDir
+ @TestJetty
+ public void server() throws Exception {
+ String dir = TestDirHelper.getTestDir().getAbsolutePath();
+
+ Configuration httpfsConf = new Configuration(false);
+ HttpFSServerWebApp server = new HttpFSServerWebApp(dir, dir, dir, dir, httpfsConf);
+ server.init();
+ server.destroy();
+ }
+
+ public static class MockGroups implements Service,Groups {
+
+ @Override
+ public void init(org.apache.hadoop.lib.server.Server server) throws ServiceException {
+ }
+
+ @Override
+ public void postInit() throws ServiceException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ @Override
+ public Class[] getServiceDependencies() {
+ return new Class[0];
+ }
+
+ @Override
+ public Class getInterface() {
+ return Groups.class;
+ }
+
+ @Override
+ public void serverStatusChange(org.apache.hadoop.lib.server.Server.Status oldStatus,
+ org.apache.hadoop.lib.server.Server.Status newStatus) throws ServiceException {
+ }
+
+ @Override
+ public List getGroups(String user) throws IOException {
+ return Arrays.asList(HadoopUsersConfTestHelper.getHadoopUserGroups(user));
+ }
+
+ }
+ private void createHttpFSServer() throws Exception {
+ File homeDir = TestDirHelper.getTestDir();
+ assertTrue(new File(homeDir, "conf").mkdir());
+ assertTrue(new File(homeDir, "log").mkdir());
+ assertTrue(new File(homeDir, "temp").mkdir());
+ HttpFSServerWebApp.setHomeDirForCurrentThread(homeDir.getAbsolutePath());
+
+ File secretFile = new File(new File(homeDir, "conf"), "secret");
+ Writer w = new FileWriter(secretFile);
+ w.write("secret");
+ w.close();
+
+ //HDFS configuration
+ File hadoopConfDir = new File(new File(homeDir, "conf"), "hadoop-conf");
+ hadoopConfDir.mkdirs();
+ String fsDefaultName = TestHdfsHelper.getHdfsConf().get(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY);
+ Configuration conf = new Configuration(false);
+ conf.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, fsDefaultName);
+ File hdfsSite = new File(hadoopConfDir, "hdfs-site.xml");
+ OutputStream os = new FileOutputStream(hdfsSite);
+ conf.writeXml(os);
+ os.close();
+
+ //HTTPFS configuration
+ conf = new Configuration(false);
+ conf.set("httpfs.services.ext", MockGroups.class.getName());
+ conf.set("httpfs.admin.group", HadoopUsersConfTestHelper.
+ getHadoopUserGroups(HadoopUsersConfTestHelper.getHadoopUsers()[0])[0]);
+ conf.set("httpfs.proxyuser." + HadoopUsersConfTestHelper.getHadoopProxyUser() + ".groups",
+ HadoopUsersConfTestHelper.getHadoopProxyUserGroups());
+ conf.set("httpfs.proxyuser." + HadoopUsersConfTestHelper.getHadoopProxyUser() + ".hosts",
+ HadoopUsersConfTestHelper.getHadoopProxyUserHosts());
+ conf.set("httpfs.authentication.signature.secret.file", secretFile.getAbsolutePath());
+ File httpfsSite = new File(new File(homeDir, "conf"), "httpfs-site.xml");
+ os = new FileOutputStream(httpfsSite);
+ conf.writeXml(os);
+ os.close();
+
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ URL url = cl.getResource("webapp");
+ WebAppContext context = new WebAppContext(url.getPath(), "/webhdfs");
+ Server server = TestJettyHelper.getJettyServer();
+ server.addHandler(context);
+ server.start();
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void instrumentation() throws Exception {
+ createHttpFSServer();
+
+ URL url = new URL(TestJettyHelper.getJettyURL(),
+ MessageFormat.format("/webhdfs/v1?user.name={0}&op=instrumentation", "nobody"));
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_UNAUTHORIZED);
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ MessageFormat.format("/webhdfs/v1?user.name={0}&op=instrumentation",
+ HadoopUsersConfTestHelper.getHadoopUsers()[0]));
+ conn = (HttpURLConnection) url.openConnection();
+ assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ String line = reader.readLine();
+ reader.close();
+ assertTrue(line.contains("\"counters\":{"));
+
+ url = new URL(TestJettyHelper.getJettyURL(),
+ MessageFormat.format("/webhdfs/v1/foo?user.name={0}&op=instrumentation",
+ HadoopUsersConfTestHelper.getHadoopUsers()[0]));
+ conn = (HttpURLConnection) url.openConnection();
+ assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_BAD_REQUEST);
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testHdfsAccess() throws Exception {
+ createHttpFSServer();
+
+ String user = HadoopUsersConfTestHelper.getHadoopUsers()[0];
+ URL url = new URL(TestJettyHelper.getJettyURL(),
+ MessageFormat.format("/webhdfs/v1/?user.name={0}&op=liststatus", user));
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ reader.readLine();
+ reader.close();
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testGlobFilter() throws Exception {
+ createHttpFSServer();
+
+ FileSystem fs = FileSystem.get(TestHdfsHelper.getHdfsConf());
+ fs.mkdirs(new Path("/tmp"));
+ fs.create(new Path("/tmp/foo.txt")).close();
+
+ String user = HadoopUsersConfTestHelper.getHadoopUsers()[0];
+ URL url = new URL(TestJettyHelper.getJettyURL(),
+ MessageFormat.format("/webhdfs/v1/tmp?user.name={0}&op=liststatus&filter=f*", user));
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ reader.readLine();
+ reader.close();
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testPutNoOperation() throws Exception {
+ createHttpFSServer();
+
+ String user = HadoopUsersConfTestHelper.getHadoopUsers()[0];
+ URL url = new URL(TestJettyHelper.getJettyURL(),
+ MessageFormat.format("/webhdfs/v1/foo?user.name={0}", user));
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setRequestMethod("PUT");
+ assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_BAD_REQUEST);
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSWithKerberos.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSWithKerberos.java
new file mode 100644
index 00000000000..87fdb3dccdf
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/fs/http/server/TestHttpFSWithKerberos.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.http.server;
+
+import junit.framework.Assert;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
+import org.apache.hadoop.fs.DelegationTokenRenewer;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.http.client.HttpFSFileSystem;
+import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator;
+import org.apache.hadoop.hdfs.web.WebHdfsFileSystem;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.test.HFSTestCase;
+import org.apache.hadoop.test.KerberosTestUtils;
+import org.apache.hadoop.test.TestDir;
+import org.apache.hadoop.test.TestDirHelper;
+import org.apache.hadoop.test.TestHdfs;
+import org.apache.hadoop.test.TestHdfsHelper;
+import org.apache.hadoop.test.TestJetty;
+import org.apache.hadoop.test.TestJettyHelper;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.junit.After;
+import org.junit.Test;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.webapp.WebAppContext;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.security.PrivilegedExceptionAction;
+import java.util.concurrent.Callable;
+
+public class TestHttpFSWithKerberos extends HFSTestCase {
+
+ @After
+ public void resetUGI() {
+ Configuration conf = new Configuration();
+ UserGroupInformation.setConfiguration(conf);
+ }
+
+ private void createHttpFSServer() throws Exception {
+ File homeDir = TestDirHelper.getTestDir();
+ Assert.assertTrue(new File(homeDir, "conf").mkdir());
+ Assert.assertTrue(new File(homeDir, "log").mkdir());
+ Assert.assertTrue(new File(homeDir, "temp").mkdir());
+ HttpFSServerWebApp.setHomeDirForCurrentThread(homeDir.getAbsolutePath());
+
+ File secretFile = new File(new File(homeDir, "conf"), "secret");
+ Writer w = new FileWriter(secretFile);
+ w.write("secret");
+ w.close();
+
+ //HDFS configuration
+ File hadoopConfDir = new File(new File(homeDir, "conf"), "hadoop-conf");
+ hadoopConfDir.mkdirs();
+ String fsDefaultName = TestHdfsHelper.getHdfsConf()
+ .get(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY);
+ Configuration conf = new Configuration(false);
+ conf.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, fsDefaultName);
+ File hdfsSite = new File(hadoopConfDir, "hdfs-site.xml");
+ OutputStream os = new FileOutputStream(hdfsSite);
+ conf.writeXml(os);
+ os.close();
+
+ conf = new Configuration(false);
+ conf.set("httpfs.proxyuser.client.hosts", "*");
+ conf.set("httpfs.proxyuser.client.groups", "*");
+
+ conf.set("httpfs.authentication.type", "kerberos");
+
+ conf.set("httpfs.authentication.signature.secret.file",
+ secretFile.getAbsolutePath());
+ File httpfsSite = new File(new File(homeDir, "conf"), "httpfs-site.xml");
+ os = new FileOutputStream(httpfsSite);
+ conf.writeXml(os);
+ os.close();
+
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ URL url = cl.getResource("webapp");
+ WebAppContext context = new WebAppContext(url.getPath(), "/webhdfs");
+ Server server = TestJettyHelper.getJettyServer();
+ server.addHandler(context);
+ server.start();
+ HttpFSServerWebApp.get().setAuthority(TestJettyHelper.getAuthority());
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testValidHttpFSAccess() throws Exception {
+ createHttpFSServer();
+
+ KerberosTestUtils.doAsClient(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ URL url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETHOMEDIRECTORY");
+ AuthenticatedURL aUrl = new AuthenticatedURL();
+ AuthenticatedURL.Token aToken = new AuthenticatedURL.Token();
+ HttpURLConnection conn = aUrl.openConnection(url, aToken);
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+ return null;
+ }
+ });
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testInvalidadHttpFSAccess() throws Exception {
+ createHttpFSServer();
+
+ URL url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETHOMEDIRECTORY");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ Assert.assertEquals(conn.getResponseCode(),
+ HttpURLConnection.HTTP_UNAUTHORIZED);
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testDelegationTokenHttpFSAccess() throws Exception {
+ createHttpFSServer();
+
+ KerberosTestUtils.doAsClient(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ //get delegation token doing SPNEGO authentication
+ URL url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETDELEGATIONTOKEN");
+ AuthenticatedURL aUrl = new AuthenticatedURL();
+ AuthenticatedURL.Token aToken = new AuthenticatedURL.Token();
+ HttpURLConnection conn = aUrl.openConnection(url, aToken);
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+ JSONObject json = (JSONObject) new JSONParser()
+ .parse(new InputStreamReader(conn.getInputStream()));
+ json =
+ (JSONObject) json
+ .get(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON);
+ String tokenStr = (String) json
+ .get(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON);
+
+ //access httpfs using the delegation token
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETHOMEDIRECTORY&delegation=" +
+ tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+
+ //try to renew the delegation token without SPNEGO credentials
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=RENEWDELEGATIONTOKEN&token=" + tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("PUT");
+ Assert.assertEquals(conn.getResponseCode(),
+ HttpURLConnection.HTTP_UNAUTHORIZED);
+
+ //renew the delegation token with SPNEGO credentials
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=RENEWDELEGATIONTOKEN&token=" + tokenStr);
+ conn = aUrl.openConnection(url, aToken);
+ conn.setRequestMethod("PUT");
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+
+ //cancel delegation token, no need for SPNEGO credentials
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=CANCELDELEGATIONTOKEN&token=" +
+ tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestMethod("PUT");
+ Assert.assertEquals(conn.getResponseCode(), HttpURLConnection.HTTP_OK);
+
+ //try to access httpfs with the canceled delegation token
+ url = new URL(TestJettyHelper.getJettyURL(),
+ "/webhdfs/v1/?op=GETHOMEDIRECTORY&delegation=" +
+ tokenStr);
+ conn = (HttpURLConnection) url.openConnection();
+ Assert.assertEquals(conn.getResponseCode(),
+ HttpURLConnection.HTTP_UNAUTHORIZED);
+ return null;
+ }
+ });
+ }
+
+ @SuppressWarnings("deprecation")
+ private void testDelegationTokenWithFS(Class fileSystemClass)
+ throws Exception {
+ createHttpFSServer();
+ Configuration conf = new Configuration();
+ conf.set("fs.webhdfs.impl", fileSystemClass.getName());
+ conf.set("fs.hdfs.impl.disable.cache", "true");
+ URI uri = new URI( "webhdfs://" +
+ TestJettyHelper.getJettyURL().toURI().getAuthority());
+ FileSystem fs = FileSystem.get(uri, conf);
+ Token> token = fs.getDelegationToken("foo");
+ fs.close();
+ fs = FileSystem.get(uri, conf);
+ ((DelegationTokenRenewer.Renewable) fs).setDelegationToken(token);
+ fs.listStatus(new Path("/"));
+ fs.close();
+ }
+
+ private void testDelegationTokenWithinDoAs(
+ final Class fileSystemClass, boolean proxyUser) throws Exception {
+ Configuration conf = new Configuration();
+ conf.set("hadoop.security.authentication", "kerberos");
+ UserGroupInformation.setConfiguration(conf);
+ UserGroupInformation.loginUserFromKeytab("client",
+ "/Users/tucu/tucu.keytab");
+ UserGroupInformation ugi = UserGroupInformation.getLoginUser();
+ if (proxyUser) {
+ ugi = UserGroupInformation.createProxyUser("foo", ugi);
+ }
+ conf = new Configuration();
+ UserGroupInformation.setConfiguration(conf);
+ ugi.doAs(
+ new PrivilegedExceptionAction() {
+ @Override
+ public Void run() throws Exception {
+ testDelegationTokenWithFS(fileSystemClass);
+ return null;
+ }
+ });
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testDelegationTokenWithHttpFSFileSystem() throws Exception {
+ testDelegationTokenWithinDoAs(HttpFSFileSystem.class, false);
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testDelegationTokenWithWebhdfsFileSystem() throws Exception {
+ testDelegationTokenWithinDoAs(WebHdfsFileSystem.class, false);
+ }
+
+ @Test
+ @TestDir
+ @TestJetty
+ @TestHdfs
+ public void testDelegationTokenWithHttpFSFileSystemProxyUser()
+ throws Exception {
+ testDelegationTokenWithinDoAs(HttpFSFileSystem.class, true);
+ }
+
+ // TODO: WebHdfsFilesystem does work with ProxyUser HDFS-3509
+ // @Test
+ // @TestDir
+ // @TestJetty
+ // @TestHdfs
+ // public void testDelegationTokenWithWebhdfsFileSystemProxyUser()
+ // throws Exception {
+ // testDelegationTokenWithinDoAs(WebHdfsFileSystem.class, true);
+ // }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/lib/service/security/TestDelegationTokenManagerService.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/lib/service/security/TestDelegationTokenManagerService.java
new file mode 100644
index 00000000000..480d04c8f42
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/lib/service/security/TestDelegationTokenManagerService.java
@@ -0,0 +1,83 @@
+/**
+ * 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.lib.service.security;
+
+import junit.framework.Assert;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.http.server.HttpFSServerWebApp;
+import org.apache.hadoop.lib.server.Server;
+import org.apache.hadoop.lib.service.DelegationTokenManager;
+import org.apache.hadoop.lib.service.DelegationTokenManagerException;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.test.HTestCase;
+import org.apache.hadoop.test.TestDir;
+import org.apache.hadoop.test.TestDirHelper;
+import org.apache.hadoop.util.StringUtils;
+import org.junit.Test;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+
+public class TestDelegationTokenManagerService extends HTestCase {
+
+ @Test
+ @TestDir
+ public void service() throws Exception {
+ String dir = TestDirHelper.getTestDir().getAbsolutePath();
+ Configuration conf = new Configuration(false);
+ conf.set("server.services", StringUtils.join(",",
+ Arrays.asList(DelegationTokenManagerService.class.getName())));
+ Server server = new Server("server", dir, dir, dir, dir, conf);
+ server.init();
+ DelegationTokenManager tm = server.get(DelegationTokenManager.class);
+ Assert.assertNotNull(tm);
+ server.destroy();
+ }
+
+ @Test
+ @TestDir
+ @SuppressWarnings("unchecked")
+ public void tokens() throws Exception {
+ String dir = TestDirHelper.getTestDir().getAbsolutePath();
+ Configuration conf = new Configuration(false);
+ conf.set("server.services", StringUtils.join(",",
+ Arrays.asList(DelegationTokenManagerService.class.getName())));
+ HttpFSServerWebApp server = new HttpFSServerWebApp(dir, dir, dir, dir, conf);
+ server.setAuthority(new InetSocketAddress(InetAddress.getLocalHost(), 14000));
+ server.init();
+ DelegationTokenManager tm = server.get(DelegationTokenManager.class);
+ Token token = tm.createToken(UserGroupInformation.getCurrentUser(), "foo");
+ Assert.assertNotNull(token);
+ tm.verifyToken(token);
+ Assert.assertTrue(tm.renewToken(token, "foo") > System.currentTimeMillis());
+ tm.cancelToken(token, "foo");
+ try {
+ tm.verifyToken(token);
+ Assert.fail();
+ } catch (DelegationTokenManagerException ex) {
+ //NOP
+ } catch (Exception ex) {
+ Assert.fail();
+ }
+ server.destroy();
+ }
+
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/test/KerberosTestUtils.java b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/test/KerberosTestUtils.java
new file mode 100644
index 00000000000..4d289ad1ddd
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs-httpfs/src/test/java/org/apache/hadoop/test/KerberosTestUtils.java
@@ -0,0 +1,138 @@
+/**
+ * Licensed 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. See accompanying LICENSE file.
+ */
+package org.apache.hadoop.test;
+
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+
+import org.apache.hadoop.security.authentication.util.KerberosUtil;
+
+import java.io.File;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Test helper class for Java Kerberos setup.
+ */
+public class KerberosTestUtils {
+ private static final String PREFIX = "httpfs.test.";
+
+ public static final String REALM = PREFIX + "kerberos.realm";
+
+ public static final String CLIENT_PRINCIPAL =
+ PREFIX + "kerberos.client.principal";
+
+ public static final String SERVER_PRINCIPAL =
+ PREFIX + "kerberos.server.principal";
+
+ public static final String KEYTAB_FILE = PREFIX + "kerberos.keytab.file";
+
+ public static String getRealm() {
+ return System.getProperty(REALM, "LOCALHOST");
+ }
+
+ public static String getClientPrincipal() {
+ return System.getProperty(CLIENT_PRINCIPAL, "client") + "@" + getRealm();
+ }
+
+ public static String getServerPrincipal() {
+ return System.getProperty(SERVER_PRINCIPAL,
+ "HTTP/localhost") + "@" + getRealm();
+ }
+
+ public static String getKeytabFile() {
+ String keytabFile =
+ new File(System.getProperty("user.home"),
+ System.getProperty("user.name") + ".keytab").toString();
+ return System.getProperty(KEYTAB_FILE, keytabFile);
+ }
+
+ private static class KerberosConfiguration extends Configuration {
+ private String principal;
+
+ public KerberosConfiguration(String principal) {
+ this.principal = principal;
+ }
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map options = new HashMap();
+ options.put("keyTab", KerberosTestUtils.getKeytabFile());
+ options.put("principal", principal);
+ options.put("useKeyTab", "true");
+ options.put("storeKey", "true");
+ options.put("doNotPrompt", "true");
+ options.put("useTicketCache", "true");
+ options.put("renewTGT", "true");
+ options.put("refreshKrb5Config", "true");
+ options.put("isInitiator", "true");
+ String ticketCache = System.getenv("KRB5CCNAME");
+ if (ticketCache != null) {
+ options.put("ticketCache", ticketCache);
+ }
+ options.put("debug", "true");
+
+ return new AppConfigurationEntry[]{
+ new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(),
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+ options),};
+ }
+ }
+
+ public static T doAs(String principal, final Callable callable)
+ throws Exception {
+ LoginContext loginContext = null;
+ try {
+ Set principals = new HashSet();
+ principals.add(
+ new KerberosPrincipal(KerberosTestUtils.getClientPrincipal()));
+ Subject subject = new Subject(false, principals, new HashSet
+
+ dfs.metrics.percentiles.intervals
+
+
+ Comma-delimited set of integers denoting the desired rollover intervals
+ (in seconds) for percentile latency metrics on the Namenode and Datanode.
+ By default, percentile latency metrics are disabled.
+
+
+
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeMetrics.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeMetrics.java
index 81748ba2cff..4921f0307eb 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeMetrics.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/datanode/TestDataNodeMetrics.java
@@ -18,21 +18,26 @@
package org.apache.hadoop.hdfs.server.datanode;
import static org.apache.hadoop.test.MetricsAsserts.assertCounter;
+import static org.apache.hadoop.test.MetricsAsserts.assertQuantileGauges;
import static org.apache.hadoop.test.MetricsAsserts.getLongCounter;
import static org.apache.hadoop.test.MetricsAsserts.getMetrics;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
-import java.util.Random;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hdfs.DFSConfigKeys;
+import org.apache.hadoop.hdfs.DFSOutputStream;
import org.apache.hadoop.hdfs.DFSTestUtil;
import org.apache.hadoop.hdfs.DistributedFileSystem;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
import org.apache.hadoop.metrics2.MetricsRecordBuilder;
import org.junit.Test;
@@ -59,8 +64,10 @@ public class TestDataNodeMetrics {
}
@Test
- public void testSendDataPacket() throws Exception {
+ public void testSendDataPacketMetrics() throws Exception {
Configuration conf = new HdfsConfiguration();
+ final int interval = 1;
+ conf.set(DFSConfigKeys.DFS_METRICS_PERCENTILES_INTERVALS_KEY, "" + interval);
MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).build();
try {
FileSystem fs = cluster.getFileSystem();
@@ -73,64 +80,110 @@ public class TestDataNodeMetrics {
assertEquals(datanodes.size(), 1);
DataNode datanode = datanodes.get(0);
MetricsRecordBuilder rb = getMetrics(datanode.getMetrics().name());
-
// Expect 2 packets, 1 for the 1 byte read, 1 for the empty packet
// signaling the end of the block
assertCounter("SendDataPacketTransferNanosNumOps", (long)2, rb);
assertCounter("SendDataPacketBlockedOnNetworkNanosNumOps", (long)2, rb);
+ // Wait for at least 1 rollover
+ Thread.sleep((interval + 1) * 1000);
+ // Check that the sendPacket percentiles rolled to non-zero values
+ String sec = interval + "s";
+ assertQuantileGauges("SendDataPacketBlockedOnNetworkNanos" + sec, rb);
+ assertQuantileGauges("SendDataPacketTransferNanos" + sec, rb);
} finally {
if (cluster != null) {cluster.shutdown();}
}
}
@Test
- public void testFlushMetric() throws Exception {
+ public void testReceivePacketMetrics() throws Exception {
Configuration conf = new HdfsConfiguration();
- MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).build();
+ final int interval = 1;
+ conf.set(DFSConfigKeys.DFS_METRICS_PERCENTILES_INTERVALS_KEY, "" + interval);
+ MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).build();
try {
cluster.waitActive();
DistributedFileSystem fs = (DistributedFileSystem) cluster.getFileSystem();
Path testFile = new Path("/testFlushNanosMetric.txt");
- DFSTestUtil.createFile(fs, testFile, 1, (short)1, new Random().nextLong());
-
+ FSDataOutputStream fout = fs.create(testFile);
+ fout.write(new byte[1]);
+ fout.hsync();
+ fout.close();
List datanodes = cluster.getDataNodes();
DataNode datanode = datanodes.get(0);
MetricsRecordBuilder dnMetrics = getMetrics(datanode.getMetrics().name());
- // Expect 2 flushes, 1 for the flush that occurs after writing, 1 that occurs
- // on closing the data and metadata files.
+ // Expect two flushes, 1 for the flush that occurs after writing,
+ // 1 that occurs on closing the data and metadata files.
assertCounter("FlushNanosNumOps", 2L, dnMetrics);
+ // Expect two syncs, one from the hsync, one on close.
+ assertCounter("FsyncNanosNumOps", 2L, dnMetrics);
+ // Wait for at least 1 rollover
+ Thread.sleep((interval + 1) * 1000);
+ // Check the receivePacket percentiles that should be non-zero
+ String sec = interval + "s";
+ assertQuantileGauges("FlushNanos" + sec, dnMetrics);
+ assertQuantileGauges("FsyncNanos" + sec, dnMetrics);
} finally {
if (cluster != null) {cluster.shutdown();}
}
}
+ /**
+ * Tests that round-trip acks in a datanode write pipeline are correctly
+ * measured.
+ */
@Test
public void testRoundTripAckMetric() throws Exception {
- final int DATANODE_COUNT = 2;
-
+ final int datanodeCount = 2;
+ final int interval = 1;
Configuration conf = new HdfsConfiguration();
- MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(DATANODE_COUNT).build();
+ conf.set(DFSConfigKeys.DFS_METRICS_PERCENTILES_INTERVALS_KEY, "" + interval);
+ MiniDFSCluster cluster = new MiniDFSCluster.Builder(conf).numDataNodes(
+ datanodeCount).build();
try {
cluster.waitActive();
- DistributedFileSystem fs = (DistributedFileSystem) cluster.getFileSystem();
-
+ FileSystem fs = cluster.getFileSystem();
+ // Open a file and get the head of the pipeline
Path testFile = new Path("/testRoundTripAckMetric.txt");
- DFSTestUtil.createFile(fs, testFile, 1, (short)DATANODE_COUNT,
- new Random().nextLong());
-
- boolean foundNonzeroPacketAckNumOps = false;
+ FSDataOutputStream fsout = fs.create(testFile, (short) datanodeCount);
+ DFSOutputStream dout = (DFSOutputStream) fsout.getWrappedStream();
+ // Slow down the writes to catch the write pipeline
+ dout.setChunksPerPacket(5);
+ dout.setArtificialSlowdown(3000);
+ fsout.write(new byte[10000]);
+ DatanodeInfo[] pipeline = null;
+ int count = 0;
+ while (pipeline == null && count < 5) {
+ pipeline = dout.getPipeline();
+ System.out.println("Waiting for pipeline to be created.");
+ Thread.sleep(1000);
+ count++;
+ }
+ // Get the head node that should be receiving downstream acks
+ DatanodeInfo headInfo = pipeline[0];
+ DataNode headNode = null;
for (DataNode datanode : cluster.getDataNodes()) {
- MetricsRecordBuilder dnMetrics = getMetrics(datanode.getMetrics().name());
- if (getLongCounter("PacketAckRoundTripTimeNanosNumOps", dnMetrics) > 0) {
- foundNonzeroPacketAckNumOps = true;
+ if (datanode.getDatanodeId().equals(headInfo)) {
+ headNode = datanode;
+ break;
}
}
- assertTrue(
- "Expected at least one datanode to have reported PacketAckRoundTripTimeNanos metric",
- foundNonzeroPacketAckNumOps);
+ assertNotNull("Could not find the head of the datanode write pipeline",
+ headNode);
+ // Close the file and wait for the metrics to rollover
+ Thread.sleep((interval + 1) * 1000);
+ // Check the ack was received
+ MetricsRecordBuilder dnMetrics = getMetrics(headNode.getMetrics()
+ .name());
+ assertTrue("Expected non-zero number of acks",
+ getLongCounter("PacketAckRoundTripTimeNanosNumOps", dnMetrics) > 0);
+ assertQuantileGauges("PacketAckRoundTripTimeNanos" + interval
+ + "s", dnMetrics);
} finally {
- if (cluster != null) {cluster.shutdown();}
+ if (cluster != null) {
+ cluster.shutdown();
+ }
}
}
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/metrics/TestNameNodeMetrics.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/metrics/TestNameNodeMetrics.java
index d29bd4ecb5d..c4d659904e7 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/metrics/TestNameNodeMetrics.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/metrics/TestNameNodeMetrics.java
@@ -19,6 +19,7 @@ package org.apache.hadoop.hdfs.server.namenode.metrics;
import static org.apache.hadoop.test.MetricsAsserts.assertCounter;
import static org.apache.hadoop.test.MetricsAsserts.assertGauge;
+import static org.apache.hadoop.test.MetricsAsserts.assertQuantileGauges;
import static org.apache.hadoop.test.MetricsAsserts.getMetrics;
import static org.junit.Assert.assertTrue;
@@ -63,6 +64,9 @@ public class TestNameNodeMetrics {
// Number of datanodes in the cluster
private static final int DATANODE_COUNT = 3;
private static final int WAIT_GAUGE_VALUE_RETRIES = 20;
+
+ // Rollover interval of percentile metrics (in seconds)
+ private static final int PERCENTILES_INTERVAL = 1;
static {
CONF.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, 100);
@@ -71,6 +75,8 @@ public class TestNameNodeMetrics {
DFS_REPLICATION_INTERVAL);
CONF.setInt(DFSConfigKeys.DFS_NAMENODE_REPLICATION_INTERVAL_KEY,
DFS_REPLICATION_INTERVAL);
+ CONF.set(DFSConfigKeys.DFS_METRICS_PERCENTILES_INTERVALS_KEY,
+ "" + PERCENTILES_INTERVAL);
((Log4JLogger)LogFactory.getLog(MetricsAsserts.class))
.getLogger().setLevel(Level.DEBUG);
@@ -352,4 +358,24 @@ public class TestNameNodeMetrics {
assertGauge("TransactionsSinceLastCheckpoint", 1L, getMetrics(NS_METRICS));
assertGauge("TransactionsSinceLastLogRoll", 1L, getMetrics(NS_METRICS));
}
+
+ /**
+ * Tests that the sync and block report metrics get updated on cluster
+ * startup.
+ */
+ @Test
+ public void testSyncAndBlockReportMetric() throws Exception {
+ MetricsRecordBuilder rb = getMetrics(NN_METRICS);
+ // We have one sync when the cluster starts up, just opening the journal
+ assertCounter("SyncsNumOps", 1L, rb);
+ // Each datanode reports in when the cluster comes up
+ assertCounter("BlockReportNumOps", (long)DATANODE_COUNT, rb);
+
+ // Sleep for an interval+slop to let the percentiles rollover
+ Thread.sleep((PERCENTILES_INTERVAL+1)*1000);
+
+ // Check that the percentiles were updated
+ assertQuantileGauges("Syncs1s", rb);
+ assertQuantileGauges("BlockReport1s", rb);
+ }
}
From d87b545165f9442f614665521ce04424af1490e8 Mon Sep 17 00:00:00 2001
From: Thomas Graves
Date: Fri, 27 Jul 2012 01:48:09 +0000
Subject: [PATCH 26/39] MAPREDUCE-4423. Potential infinite fetching of map
output (Robert Evans via tgraves)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1366258 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-mapreduce-project/CHANGES.txt | 3 +
.../hadoop/mapreduce/task/reduce/Fetcher.java | 76 ++++---
.../mapreduce/task/reduce/TestFetcher.java | 188 ++++++++++++++++++
3 files changed, 239 insertions(+), 28 deletions(-)
create mode 100644 hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/task/reduce/TestFetcher.java
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index 2cc69e6c78d..620ccb84a33 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -757,6 +757,9 @@ Release 0.23.3 - UNRELEASED
MAPREDUCE-4467. IndexCache failures due to missing synchronization
(Kihwal Lee via tgraves)
+ MAPREDUCE-4423. Potential infinite fetching of map output (Robert Evans
+ via tgraves)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
index 64bc43e51b6..6c527ae1ce5 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/task/reduce/Fetcher.java
@@ -21,11 +21,12 @@ import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
-import java.net.HttpURLConnection;
import java.net.URLConnection;
import java.security.GeneralSecurityException;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -53,7 +54,8 @@ import org.apache.hadoop.mapreduce.task.reduce.MapOutput.Type;
import org.apache.hadoop.util.Progressable;
import org.apache.hadoop.util.ReflectionUtils;
-@SuppressWarnings({"deprecation"})
+import com.google.common.annotations.VisibleForTesting;
+
class Fetcher extends Thread {
private static final Log LOG = LogFactory.getLog(Fetcher.class);
@@ -199,6 +201,7 @@ class Fetcher extends Thread {
}
}
+ @VisibleForTesting
protected HttpURLConnection openConnection(URL url) throws IOException {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (sslShuffle) {
@@ -209,17 +212,18 @@ class Fetcher extends Thread {
throw new IOException(ex);
}
httpsConn.setHostnameVerifier(sslFactory.getHostnameVerifier());
- }
- return conn;
}
-
+ return conn;
+ }
+
/**
* The crux of the matter...
*
* @param host {@link MapHost} from which we need to
* shuffle available map-outputs.
*/
- private void copyFromHost(MapHost host) throws IOException {
+ @VisibleForTesting
+ protected void copyFromHost(MapHost host) throws IOException {
// Get completed maps on 'host'
List maps = scheduler.getMapsForHost(host);
@@ -229,9 +233,9 @@ class Fetcher extends Thread {
return;
}
- LOG.debug("Fetcher " + id + " going to fetch from " + host);
- for (TaskAttemptID tmp: maps) {
- LOG.debug(tmp);
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Fetcher " + id + " going to fetch from " + host + " for: "
+ + maps);
}
// List of maps to be fetched yet
@@ -304,17 +308,25 @@ class Fetcher extends Thread {
try {
// Loop through available map-outputs and fetch them
- // On any error, good becomes false and we exit after putting back
- // the remaining maps to the yet_to_be_fetched list
- boolean good = true;
- while (!remaining.isEmpty() && good) {
- good = copyMapOutput(host, input, remaining);
+ // On any error, faildTasks is not null and we exit
+ // after putting back the remaining maps to the
+ // yet_to_be_fetched list and marking the failed tasks.
+ TaskAttemptID[] failedTasks = null;
+ while (!remaining.isEmpty() && failedTasks == null) {
+ failedTasks = copyMapOutput(host, input, remaining);
+ }
+
+ if(failedTasks != null && failedTasks.length > 0) {
+ LOG.warn("copyMapOutput failed for tasks "+Arrays.toString(failedTasks));
+ for(TaskAttemptID left: failedTasks) {
+ scheduler.copyFailed(left, host, true);
+ }
}
IOUtils.cleanup(LOG, input);
// Sanity check
- if (good && !remaining.isEmpty()) {
+ if (failedTasks == null && !remaining.isEmpty()) {
throw new IOException("server didn't return all expected map outputs: "
+ remaining.size() + " left.");
}
@@ -323,10 +335,11 @@ class Fetcher extends Thread {
scheduler.putBackKnownMapOutput(host, left);
}
}
-
- }
+ }
- private boolean copyMapOutput(MapHost host,
+ private static TaskAttemptID[] EMPTY_ATTEMPT_ID_ARRAY = new TaskAttemptID[0];
+
+ private TaskAttemptID[] copyMapOutput(MapHost host,
DataInputStream input,
Set remaining) {
MapOutput mapOutput = null;
@@ -348,18 +361,21 @@ class Fetcher extends Thread {
} catch (IllegalArgumentException e) {
badIdErrs.increment(1);
LOG.warn("Invalid map id ", e);
- return false;
+ //Don't know which one was bad, so consider all of them as bad
+ return remaining.toArray(new TaskAttemptID[remaining.size()]);
}
// Do some basic sanity verification
if (!verifySanity(compressedLength, decompressedLength, forReduce,
remaining, mapId)) {
- return false;
+ return new TaskAttemptID[] {mapId};
}
- LOG.debug("header: " + mapId + ", len: " + compressedLength +
- ", decomp len: " + decompressedLength);
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("header: " + mapId + ", len: " + compressedLength +
+ ", decomp len: " + decompressedLength);
+ }
// Get the location for the map output - either in-memory or on-disk
mapOutput = merger.reserve(mapId, decompressedLength, id);
@@ -367,7 +383,8 @@ class Fetcher extends Thread {
// Check if we can shuffle *now* ...
if (mapOutput.getType() == Type.WAIT) {
LOG.info("fetcher#" + id + " - MergerManager returned Status.WAIT ...");
- return false;
+ //Not an error but wait to process data.
+ return EMPTY_ATTEMPT_ID_ARRAY;
}
// Go!
@@ -389,24 +406,27 @@ class Fetcher extends Thread {
// Note successful shuffle
remaining.remove(mapId);
metrics.successFetch();
- return true;
+ return null;
} catch (IOException ioe) {
ioErrs.increment(1);
if (mapId == null || mapOutput == null) {
LOG.info("fetcher#" + id + " failed to read map header" +
mapId + " decomp: " +
decompressedLength + ", " + compressedLength, ioe);
- return false;
+ if(mapId == null) {
+ return remaining.toArray(new TaskAttemptID[remaining.size()]);
+ } else {
+ return new TaskAttemptID[] {mapId};
+ }
}
- LOG.info("Failed to shuffle output of " + mapId +
+ LOG.warn("Failed to shuffle output of " + mapId +
" from " + host.getHostName(), ioe);
// Inform the shuffle-scheduler
mapOutput.abort();
- scheduler.copyFailed(mapId, host, true);
metrics.failedFetch();
- return false;
+ return new TaskAttemptID[] {mapId};
}
}
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/task/reduce/TestFetcher.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/task/reduce/TestFetcher.java
new file mode 100644
index 00000000000..097f120dd21
--- /dev/null
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/java/org/apache/hadoop/mapreduce/task/reduce/TestFetcher.java
@@ -0,0 +1,188 @@
+/**
+ * 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.mapreduce.task.reduce;
+
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+
+import javax.crypto.SecretKey;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.mapred.Counters;
+import org.apache.hadoop.mapred.JobConf;
+import org.apache.hadoop.mapred.Reporter;
+import org.apache.hadoop.mapreduce.TaskAttemptID;
+import org.apache.hadoop.mapreduce.security.SecureShuffleUtils;
+import org.apache.hadoop.mapreduce.security.token.JobTokenSecretManager;
+import org.junit.Test;
+
+/**
+ * Test that the Fetcher does what we expect it to.
+ */
+public class TestFetcher {
+ private static final Log LOG = LogFactory.getLog(TestFetcher.class);
+
+ public static class FakeFetcher extends Fetcher {
+
+ private HttpURLConnection connection;
+
+ public FakeFetcher(JobConf job, TaskAttemptID reduceId,
+ ShuffleScheduler scheduler, MergeManager merger, Reporter reporter,
+ ShuffleClientMetrics metrics, ExceptionReporter exceptionReporter,
+ SecretKey jobTokenSecret, HttpURLConnection connection) {
+ super(job, reduceId, scheduler, merger, reporter, metrics, exceptionReporter,
+ jobTokenSecret);
+ this.connection = connection;
+ }
+
+ @Override
+ protected HttpURLConnection openConnection(URL url) throws IOException {
+ if(connection != null) {
+ return connection;
+ }
+ return super.openConnection(url);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testCopyFromHostBogusHeader() throws Exception {
+ LOG.info("testCopyFromHostBogusHeader");
+ JobConf job = new JobConf();
+ TaskAttemptID id = TaskAttemptID.forName("attempt_0_1_r_1_1");
+ ShuffleScheduler ss = mock(ShuffleScheduler.class);
+ MergeManager mm = mock(MergeManager.class);
+ Reporter r = mock(Reporter.class);
+ ShuffleClientMetrics metrics = mock(ShuffleClientMetrics.class);
+ ExceptionReporter except = mock(ExceptionReporter.class);
+ SecretKey key = JobTokenSecretManager.createSecretKey(new byte[]{0,0,0,0});
+ HttpURLConnection connection = mock(HttpURLConnection.class);
+
+ Counters.Counter allErrs = mock(Counters.Counter.class);
+ when(r.getCounter(anyString(), anyString()))
+ .thenReturn(allErrs);
+
+ Fetcher underTest = new FakeFetcher(job, id, ss, mm,
+ r, metrics, except, key, connection);
+
+
+ MapHost host = new MapHost("localhost", "http://localhost:8080/");
+
+ ArrayList maps = new ArrayList(1);
+ TaskAttemptID map1ID = TaskAttemptID.forName("attempt_0_1_m_1_1");
+ maps.add(map1ID);
+ TaskAttemptID map2ID = TaskAttemptID.forName("attempt_0_1_m_2_1");
+ maps.add(map2ID);
+ when(ss.getMapsForHost(host)).thenReturn(maps);
+
+ String encHash = "vFE234EIFCiBgYs2tCXY/SjT8Kg=";
+ String replyHash = SecureShuffleUtils.generateHash(encHash.getBytes(), key);
+
+ when(connection.getResponseCode()).thenReturn(200);
+ when(connection.getHeaderField(SecureShuffleUtils.HTTP_HEADER_REPLY_URL_HASH))
+ .thenReturn(replyHash);
+ ByteArrayInputStream in = new ByteArrayInputStream(
+ "\u00010 BOGUS DATA\nBOGUS DATA\nBOGUS DATA\n".getBytes());
+ when(connection.getInputStream()).thenReturn(in);
+
+ underTest.copyFromHost(host);
+
+ verify(connection)
+ .addRequestProperty(SecureShuffleUtils.HTTP_HEADER_URL_HASH,
+ encHash);
+
+ verify(allErrs).increment(1);
+ verify(ss).copyFailed(map1ID, host, true);
+ verify(ss).copyFailed(map2ID, host, true);
+
+ verify(ss).putBackKnownMapOutput(any(MapHost.class), eq(map1ID));
+ verify(ss).putBackKnownMapOutput(any(MapHost.class), eq(map2ID));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testCopyFromHostWait() throws Exception {
+ LOG.info("testCopyFromHostWait");
+ JobConf job = new JobConf();
+ TaskAttemptID id = TaskAttemptID.forName("attempt_0_1_r_1_1");
+ ShuffleScheduler ss = mock(ShuffleScheduler.class);
+ MergeManager mm = mock(MergeManager.class);
+ Reporter r = mock(Reporter.class);
+ ShuffleClientMetrics metrics = mock(ShuffleClientMetrics.class);
+ ExceptionReporter except = mock(ExceptionReporter.class);
+ SecretKey key = JobTokenSecretManager.createSecretKey(new byte[]{0,0,0,0});
+ HttpURLConnection connection = mock(HttpURLConnection.class);
+
+ Counters.Counter allErrs = mock(Counters.Counter.class);
+ when(r.getCounter(anyString(), anyString()))
+ .thenReturn(allErrs);
+
+ Fetcher underTest = new FakeFetcher(job, id, ss, mm,
+ r, metrics, except, key, connection);
+
+
+ MapHost host = new MapHost("localhost", "http://localhost:8080/");
+
+ ArrayList maps = new ArrayList(1);
+ TaskAttemptID map1ID = TaskAttemptID.forName("attempt_0_1_m_1_1");
+ maps.add(map1ID);
+ TaskAttemptID map2ID = TaskAttemptID.forName("attempt_0_1_m_2_1");
+ maps.add(map2ID);
+ when(ss.getMapsForHost(host)).thenReturn(maps);
+
+ String encHash = "vFE234EIFCiBgYs2tCXY/SjT8Kg=";
+ String replyHash = SecureShuffleUtils.generateHash(encHash.getBytes(), key);
+
+ when(connection.getResponseCode()).thenReturn(200);
+ when(connection.getHeaderField(SecureShuffleUtils.HTTP_HEADER_REPLY_URL_HASH))
+ .thenReturn(replyHash);
+ ShuffleHeader header = new ShuffleHeader(map1ID.toString(), 10, 10, 1);
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ header.write(new DataOutputStream(bout));
+ ByteArrayInputStream in = new ByteArrayInputStream(bout.toByteArray());
+ when(connection.getInputStream()).thenReturn(in);
+ //Defaults to WAIT, which is what we want to test
+ MapOutput mapOut = new MapOutput(map1ID);
+ when(mm.reserve(any(TaskAttemptID.class), anyLong(), anyInt()))
+ .thenReturn(mapOut);
+
+ underTest.copyFromHost(host);
+
+ verify(connection)
+ .addRequestProperty(SecureShuffleUtils.HTTP_HEADER_URL_HASH,
+ encHash);
+ verify(allErrs, never()).increment(1);
+ verify(ss, never()).copyFailed(map1ID, host, true);
+ verify(ss, never()).copyFailed(map2ID, host, true);
+
+ verify(ss).putBackKnownMapOutput(any(MapHost.class), eq(map1ID));
+ verify(ss).putBackKnownMapOutput(any(MapHost.class), eq(map2ID));
+ }
+
+}
From 9c5bd764fc3faf995c4d917bec86806eda2747f7 Mon Sep 17 00:00:00 2001
From: Aaron Myers
Date: Fri, 27 Jul 2012 15:13:57 +0000
Subject: [PATCH 27/39] HADOOP-8626. Typo in default setting for
hadoop.security.group.mapping.ldap.search.filter.user. Contributed by
Jonathan Natkins.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1366410 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-common-project/hadoop-common/CHANGES.txt | 4 ++++
.../hadoop-common/src/main/resources/core-default.xml | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index 83b52fdf281..03d221c50d0 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -367,6 +367,10 @@ Branch-2 ( Unreleased changes )
HADOOP-8537. Fix TFile tests to pass even when native zlib support is not
compiled. (todd)
+ HADOOP-8626. Typo in default setting for
+ hadoop.security.group.mapping.ldap.search.filter.user. (Jonathan Natkins
+ via atm)
+
BREAKDOWN OF HDFS-3042 SUBTASKS
HADOOP-8220. ZKFailoverController doesn't handle failure to become active
diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
index 7667eecb244..93b11c008b0 100644
--- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
+++ b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
@@ -165,7 +165,7 @@
hadoop.security.group.mapping.ldap.search.filter.user
- (&(objectClass=user)(sAMAccountName={0})
+ (&(objectClass=user)(sAMAccountName={0}))
An additional filter to use when searching for LDAP users. The default will
usually be appropriate for Active Directory installations. If connecting to
From b3b72482e4476fe6ead484e85b55c766bcf998d0 Mon Sep 17 00:00:00 2001
From: Daryn Sharp
Date: Fri, 27 Jul 2012 16:39:51 +0000
Subject: [PATCH 28/39] HADOOP-8613.
AbstractDelegationTokenIdentifier#getUser() should set token auth type.
(daryn)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1366440 13f79535-47bb-0310-9956-ffa450edef68
---
.../hadoop-common/CHANGES.txt | 3 ++
.../AbstractDelegationTokenIdentifier.java | 12 +++--
.../token/delegation/TestDelegationToken.java | 48 +++++++++++++++++++
.../hadoop/hdfs/server/common/JspHelper.java | 1 -
4 files changed, 59 insertions(+), 5 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index 03d221c50d0..f01b8a5db2e 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -854,6 +854,9 @@ Release 0.23.3 - UNRELEASED
HADOOP-8551. fs -mkdir creates parent directories without the -p option
(John George via bobby)
+ HADOOP-8613. AbstractDelegationTokenIdentifier#getUser() should set token
+ auth type. (daryn)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java
index 8c3c1b2d35f..b3e367bdf25 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/token/delegation/AbstractDelegationTokenIdentifier.java
@@ -29,6 +29,7 @@ import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableUtils;
import org.apache.hadoop.security.HadoopKerberosName;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
import org.apache.hadoop.security.token.TokenIdentifier;
import com.google.common.annotations.VisibleForTesting;
@@ -88,14 +89,17 @@ extends TokenIdentifier {
if ( (owner == null) || ("".equals(owner.toString()))) {
return null;
}
+ final UserGroupInformation realUgi;
+ final UserGroupInformation ugi;
if ((realUser == null) || ("".equals(realUser.toString()))
|| realUser.equals(owner)) {
- return UserGroupInformation.createRemoteUser(owner.toString());
+ ugi = realUgi = UserGroupInformation.createRemoteUser(owner.toString());
} else {
- UserGroupInformation realUgi = UserGroupInformation
- .createRemoteUser(realUser.toString());
- return UserGroupInformation.createProxyUser(owner.toString(), realUgi);
+ realUgi = UserGroupInformation.createRemoteUser(realUser.toString());
+ ugi = UserGroupInformation.createProxyUser(owner.toString(), realUgi);
}
+ realUgi.setAuthenticationMethod(AuthenticationMethod.TOKEN);
+ return ugi;
}
public Text getOwner() {
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/TestDelegationToken.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/TestDelegationToken.java
index ed07c972b56..c1dd00a4d7d 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/TestDelegationToken.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/TestDelegationToken.java
@@ -40,6 +40,8 @@ import org.apache.hadoop.io.DataOutputBuffer;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.security.AccessControlException;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
import org.apache.hadoop.security.token.SecretManager;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.SecretManager.InvalidToken;
@@ -171,6 +173,52 @@ public class TestDelegationToken {
}
}
+ @Test
+ public void testGetUserNullOwner() {
+ TestDelegationTokenIdentifier ident =
+ new TestDelegationTokenIdentifier(null, null, null);
+ UserGroupInformation ugi = ident.getUser();
+ assertNull(ugi);
+ }
+
+ @Test
+ public void testGetUserWithOwner() {
+ TestDelegationTokenIdentifier ident =
+ new TestDelegationTokenIdentifier(new Text("owner"), null, null);
+ UserGroupInformation ugi = ident.getUser();
+ assertNull(ugi.getRealUser());
+ assertEquals("owner", ugi.getUserName());
+ assertEquals(AuthenticationMethod.TOKEN, ugi.getAuthenticationMethod());
+ }
+
+ @Test
+ public void testGetUserWithOwnerEqualsReal() {
+ Text owner = new Text("owner");
+ TestDelegationTokenIdentifier ident =
+ new TestDelegationTokenIdentifier(owner, null, owner);
+ UserGroupInformation ugi = ident.getUser();
+ assertNull(ugi.getRealUser());
+ assertEquals("owner", ugi.getUserName());
+ assertEquals(AuthenticationMethod.TOKEN, ugi.getAuthenticationMethod());
+ }
+
+ @Test
+ public void testGetUserWithOwnerAndReal() {
+ Text owner = new Text("owner");
+ Text realUser = new Text("realUser");
+ TestDelegationTokenIdentifier ident =
+ new TestDelegationTokenIdentifier(owner, null, realUser);
+ UserGroupInformation ugi = ident.getUser();
+ assertNotNull(ugi.getRealUser());
+ assertNull(ugi.getRealUser().getRealUser());
+ assertEquals("owner", ugi.getUserName());
+ assertEquals("realUser", ugi.getRealUser().getUserName());
+ assertEquals(AuthenticationMethod.PROXY,
+ ugi.getAuthenticationMethod());
+ assertEquals(AuthenticationMethod.TOKEN,
+ ugi.getRealUser().getAuthenticationMethod());
+ }
+
@Test
public void testDelegationTokenSecretManager() throws Exception {
final TestDelegationTokenSecretManager dtSecretManager =
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/JspHelper.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/JspHelper.java
index f8bfee73a7e..d153a8f48c9 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/JspHelper.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/JspHelper.java
@@ -578,7 +578,6 @@ public class JspHelper {
ProxyUsers.authorize(ugi, request.getRemoteAddr(), conf);
}
ugi.addToken(token);
- ugi.setAuthenticationMethod(AuthenticationMethod.TOKEN);
} else {
if(remoteUser == null) {
throw new IOException("Security enabled but user not " +
From cbd59c1c50242a9ee799cfe9a33f3bdc4561c4ea Mon Sep 17 00:00:00 2001
From: Daryn Sharp
Date: Fri, 27 Jul 2012 18:02:56 +0000
Subject: [PATCH 29/39] HDFS-3553. Hftp proxy tokens are broken (daryn)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1366471 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 +
.../hadoop/hdfs/server/common/JspHelper.java | 133 +++++----
.../hdfs/server/common/TestJspHelper.java | 263 ++++++++++++++++++
3 files changed, 331 insertions(+), 67 deletions(-)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 92dccfa4dca..4c7d679ad67 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -1411,6 +1411,8 @@ Release 0.23.3 - UNRELEASED
HDFS-3696. Set chunked streaming mode in WebHdfsFileSystem write operations
to get around a Java library bug causing OutOfMemoryError. (szetszwo)
+ HDFS-3553. Hftp proxy tokens are broken (daryn)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/JspHelper.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/JspHelper.java
index d153a8f48c9..cce5f74d83c 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/JspHelper.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/common/JspHelper.java
@@ -487,12 +487,17 @@ public class JspHelper {
*/
public static UserGroupInformation getDefaultWebUser(Configuration conf
) throws IOException {
+ return UserGroupInformation.createRemoteUser(getDefaultWebUserName(conf));
+ }
+
+ private static String getDefaultWebUserName(Configuration conf
+ ) throws IOException {
String user = conf.get(
HADOOP_HTTP_STATIC_USER, DEFAULT_HADOOP_HTTP_STATIC_USER);
if (user == null || user.length() == 0) {
throw new IOException("Cannot determine UGI from request or conf");
}
- return UserGroupInformation.createRemoteUser(user);
+ return user;
}
private static InetSocketAddress getNNServiceAddress(ServletContext context,
@@ -538,64 +543,45 @@ public class JspHelper {
HttpServletRequest request, Configuration conf,
final AuthenticationMethod secureAuthMethod,
final boolean tryUgiParameter) throws IOException {
- final UserGroupInformation ugi;
+ UserGroupInformation ugi = null;
final String usernameFromQuery = getUsernameFromQuery(request, tryUgiParameter);
final String doAsUserFromQuery = request.getParameter(DoAsParam.NAME);
-
- if(UserGroupInformation.isSecurityEnabled()) {
- final String remoteUser = request.getRemoteUser();
- String tokenString = request.getParameter(DELEGATION_PARAMETER_NAME);
+ final String remoteUser;
+
+ if (UserGroupInformation.isSecurityEnabled()) {
+ remoteUser = request.getRemoteUser();
+ final String tokenString = request.getParameter(DELEGATION_PARAMETER_NAME);
if (tokenString != null) {
- Token token =
- new Token();
- token.decodeFromUrlString(tokenString);
- InetSocketAddress serviceAddress = getNNServiceAddress(context, request);
- if (serviceAddress != null) {
- SecurityUtil.setTokenService(token, serviceAddress);
- token.setKind(DelegationTokenIdentifier.HDFS_DELEGATION_KIND);
- }
- ByteArrayInputStream buf = new ByteArrayInputStream(token
- .getIdentifier());
- DataInputStream in = new DataInputStream(buf);
- DelegationTokenIdentifier id = new DelegationTokenIdentifier();
- id.readFields(in);
- if (context != null) {
- final NameNode nn = NameNodeHttpServer.getNameNodeFromContext(context);
- if (nn != null) {
- // Verify the token.
- nn.getNamesystem().verifyToken(id, token.getPassword());
- }
- }
- ugi = id.getUser();
- if (ugi.getRealUser() == null) {
- //non-proxy case
- checkUsername(ugi.getShortUserName(), usernameFromQuery);
- checkUsername(null, doAsUserFromQuery);
- } else {
- //proxy case
- checkUsername(ugi.getRealUser().getShortUserName(), usernameFromQuery);
- checkUsername(ugi.getShortUserName(), doAsUserFromQuery);
- ProxyUsers.authorize(ugi, request.getRemoteAddr(), conf);
- }
- ugi.addToken(token);
- } else {
- if(remoteUser == null) {
- throw new IOException("Security enabled but user not " +
- "authenticated by filter");
- }
- final UserGroupInformation realUgi = UserGroupInformation.createRemoteUser(remoteUser);
- checkUsername(realUgi.getShortUserName(), usernameFromQuery);
+ // Token-based connections need only verify the effective user, and
+ // disallow proxying to different user. Proxy authorization checks
+ // are not required since the checks apply to issuing a token.
+ ugi = getTokenUGI(context, request, tokenString, conf);
+ checkUsername(ugi.getShortUserName(), usernameFromQuery);
+ checkUsername(ugi.getShortUserName(), doAsUserFromQuery);
+ } else if (remoteUser == null) {
+ throw new IOException(
+ "Security enabled but user not authenticated by filter");
+ }
+ } else {
+ // Security's not on, pull from url or use default web user
+ remoteUser = (usernameFromQuery == null)
+ ? getDefaultWebUserName(conf) // not specified in request
+ : usernameFromQuery;
+ }
+
+ if (ugi == null) { // security is off, or there's no token
+ ugi = UserGroupInformation.createRemoteUser(remoteUser);
+ checkUsername(ugi.getShortUserName(), usernameFromQuery);
+ if (UserGroupInformation.isSecurityEnabled()) {
// This is not necessarily true, could have been auth'ed by user-facing
// filter
- realUgi.setAuthenticationMethod(secureAuthMethod);
- ugi = initUGI(realUgi, doAsUserFromQuery, request, true, conf);
+ ugi.setAuthenticationMethod(secureAuthMethod);
+ }
+ if (doAsUserFromQuery != null) {
+ // create and attempt to authorize a proxy user
+ ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, ugi);
+ ProxyUsers.authorize(ugi, request.getRemoteAddr(), conf);
}
- } else { // Security's not on, pull from url
- final UserGroupInformation realUgi = usernameFromQuery == null?
- getDefaultWebUser(conf) // not specified in request
- : UserGroupInformation.createRemoteUser(usernameFromQuery);
- realUgi.setAuthenticationMethod(AuthenticationMethod.SIMPLE);
- ugi = initUGI(realUgi, doAsUserFromQuery, request, false, conf);
}
if(LOG.isDebugEnabled())
@@ -603,21 +589,34 @@ public class JspHelper {
return ugi;
}
- private static UserGroupInformation initUGI(final UserGroupInformation realUgi,
- final String doAsUserFromQuery, final HttpServletRequest request,
- final boolean isSecurityEnabled, final Configuration conf
- ) throws AuthorizationException {
- final UserGroupInformation ugi;
- if (doAsUserFromQuery == null) {
- //non-proxy case
- ugi = realUgi;
- } else {
- //proxy case
- ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, realUgi);
- ugi.setAuthenticationMethod(
- isSecurityEnabled? AuthenticationMethod.PROXY: AuthenticationMethod.SIMPLE);
- ProxyUsers.authorize(ugi, request.getRemoteAddr(), conf);
+ private static UserGroupInformation getTokenUGI(ServletContext context,
+ HttpServletRequest request,
+ String tokenString,
+ Configuration conf)
+ throws IOException {
+ final Token token =
+ new Token();
+ token.decodeFromUrlString(tokenString);
+ InetSocketAddress serviceAddress = getNNServiceAddress(context, request);
+ if (serviceAddress != null) {
+ SecurityUtil.setTokenService(token, serviceAddress);
+ token.setKind(DelegationTokenIdentifier.HDFS_DELEGATION_KIND);
}
+
+ ByteArrayInputStream buf =
+ new ByteArrayInputStream(token.getIdentifier());
+ DataInputStream in = new DataInputStream(buf);
+ DelegationTokenIdentifier id = new DelegationTokenIdentifier();
+ id.readFields(in);
+ if (context != null) {
+ final NameNode nn = NameNodeHttpServer.getNameNodeFromContext(context);
+ if (nn != null) {
+ // Verify the token.
+ nn.getNamesystem().verifyToken(id, token.getPassword());
+ }
+ }
+ UserGroupInformation ugi = id.getUser();
+ ugi.addToken(token);
return ugi;
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/common/TestJspHelper.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/common/TestJspHelper.java
index c7fafdb13bc..d024bcdbf7c 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/common/TestJspHelper.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/common/TestJspHelper.java
@@ -31,8 +31,13 @@ import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
import org.apache.hadoop.hdfs.server.namenode.NameNodeHttpServer;
+import org.apache.hadoop.hdfs.web.resources.DoAsParam;
+import org.apache.hadoop.hdfs.web.resources.UserParam;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.ProxyUsers;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.TokenIdentifier;
import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
@@ -136,4 +141,262 @@ public class TestJspHelper {
Assert.assertEquals("", delegationTokenParam);
}
+ @Test
+ public void testGetUgiFromToken() throws IOException {
+ conf.set(DFSConfigKeys.FS_DEFAULT_NAME_KEY, "hdfs://localhost:4321/");
+ ServletContext context = mock(ServletContext.class);
+ String realUser = "TheDoctor";
+ String user = "TheNurse";
+ conf.set(DFSConfigKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
+ UserGroupInformation.setConfiguration(conf);
+ UserGroupInformation ugi;
+ HttpServletRequest request;
+
+ Text ownerText = new Text(user);
+ DelegationTokenIdentifier dtId = new DelegationTokenIdentifier(
+ ownerText, ownerText, new Text(realUser));
+ Token token = new Token(
+ dtId, new DummySecretManager(0, 0, 0, 0));
+ String tokenString = token.encodeToUrlString();
+
+ // token with no auth-ed user
+ request = getMockRequest(null, null, null);
+ when(request.getParameter(JspHelper.DELEGATION_PARAMETER_NAME)).thenReturn(
+ tokenString);
+ ugi = JspHelper.getUGI(context, request, conf);
+ Assert.assertNotNull(ugi.getRealUser());
+ Assert.assertEquals(ugi.getRealUser().getShortUserName(), realUser);
+ Assert.assertEquals(ugi.getShortUserName(), user);
+ checkUgiFromToken(ugi);
+
+ // token with auth-ed user
+ request = getMockRequest(realUser, null, null);
+ when(request.getParameter(JspHelper.DELEGATION_PARAMETER_NAME)).thenReturn(
+ tokenString);
+ ugi = JspHelper.getUGI(context, request, conf);
+ Assert.assertNotNull(ugi.getRealUser());
+ Assert.assertEquals(ugi.getRealUser().getShortUserName(), realUser);
+ Assert.assertEquals(ugi.getShortUserName(), user);
+ checkUgiFromToken(ugi);
+
+ // completely different user, token trumps auth
+ request = getMockRequest("rogue", null, null);
+ when(request.getParameter(JspHelper.DELEGATION_PARAMETER_NAME)).thenReturn(
+ tokenString);
+ ugi = JspHelper.getUGI(context, request, conf);
+ Assert.assertNotNull(ugi.getRealUser());
+ Assert.assertEquals(ugi.getRealUser().getShortUserName(), realUser);
+ Assert.assertEquals(ugi.getShortUserName(), user);
+ checkUgiFromToken(ugi);
+
+ // expected case
+ request = getMockRequest(null, user, null);
+ when(request.getParameter(JspHelper.DELEGATION_PARAMETER_NAME)).thenReturn(
+ tokenString);
+ ugi = JspHelper.getUGI(context, request, conf);
+ Assert.assertNotNull(ugi.getRealUser());
+ Assert.assertEquals(ugi.getRealUser().getShortUserName(), realUser);
+ Assert.assertEquals(ugi.getShortUserName(), user);
+ checkUgiFromToken(ugi);
+
+ // can't proxy with a token!
+ request = getMockRequest(null, null, "rogue");
+ when(request.getParameter(JspHelper.DELEGATION_PARAMETER_NAME)).thenReturn(
+ tokenString);
+ try {
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad request allowed");
+ } catch (IOException ioe) {
+ Assert.assertEquals(
+ "Usernames not matched: name=rogue != expected="+user,
+ ioe.getMessage());
+ }
+
+ // can't proxy with a token!
+ request = getMockRequest(null, user, "rogue");
+ when(request.getParameter(JspHelper.DELEGATION_PARAMETER_NAME)).thenReturn(
+ tokenString);
+ try {
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad request allowed");
+ } catch (IOException ioe) {
+ Assert.assertEquals(
+ "Usernames not matched: name=rogue != expected="+user,
+ ioe.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetNonProxyUgi() throws IOException {
+ conf.set(DFSConfigKeys.FS_DEFAULT_NAME_KEY, "hdfs://localhost:4321/");
+ ServletContext context = mock(ServletContext.class);
+ String realUser = "TheDoctor";
+ String user = "TheNurse";
+ conf.set(DFSConfigKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
+ UserGroupInformation.setConfiguration(conf);
+ UserGroupInformation ugi;
+ HttpServletRequest request;
+
+ // have to be auth-ed with remote user
+ request = getMockRequest(null, null, null);
+ try {
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad request allowed");
+ } catch (IOException ioe) {
+ Assert.assertEquals(
+ "Security enabled but user not authenticated by filter",
+ ioe.getMessage());
+ }
+ request = getMockRequest(null, realUser, null);
+ try {
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad request allowed");
+ } catch (IOException ioe) {
+ Assert.assertEquals(
+ "Security enabled but user not authenticated by filter",
+ ioe.getMessage());
+ }
+
+ // ugi for remote user
+ request = getMockRequest(realUser, null, null);
+ ugi = JspHelper.getUGI(context, request, conf);
+ Assert.assertNull(ugi.getRealUser());
+ Assert.assertEquals(ugi.getShortUserName(), realUser);
+ checkUgiFromAuth(ugi);
+
+ // ugi for remote user = real user
+ request = getMockRequest(realUser, realUser, null);
+ ugi = JspHelper.getUGI(context, request, conf);
+ Assert.assertNull(ugi.getRealUser());
+ Assert.assertEquals(ugi.getShortUserName(), realUser);
+ checkUgiFromAuth(ugi);
+
+ // ugi for remote user != real user
+ request = getMockRequest(realUser, user, null);
+ try {
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad request allowed");
+ } catch (IOException ioe) {
+ Assert.assertEquals(
+ "Usernames not matched: name="+user+" != expected="+realUser,
+ ioe.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetProxyUgi() throws IOException {
+ conf.set(DFSConfigKeys.FS_DEFAULT_NAME_KEY, "hdfs://localhost:4321/");
+ ServletContext context = mock(ServletContext.class);
+ String realUser = "TheDoctor";
+ String user = "TheNurse";
+ conf.set(DFSConfigKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
+
+ conf.set(ProxyUsers.CONF_HADOOP_PROXYUSER+realUser+".groups", "*");
+ conf.set(ProxyUsers.CONF_HADOOP_PROXYUSER+realUser+".hosts", "*");
+ ProxyUsers.refreshSuperUserGroupsConfiguration(conf);
+ UserGroupInformation.setConfiguration(conf);
+ UserGroupInformation ugi;
+ HttpServletRequest request;
+
+ // have to be auth-ed with remote user
+ request = getMockRequest(null, null, user);
+ try {
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad request allowed");
+ } catch (IOException ioe) {
+ Assert.assertEquals(
+ "Security enabled but user not authenticated by filter",
+ ioe.getMessage());
+ }
+ request = getMockRequest(null, realUser, user);
+ try {
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad request allowed");
+ } catch (IOException ioe) {
+ Assert.assertEquals(
+ "Security enabled but user not authenticated by filter",
+ ioe.getMessage());
+ }
+
+ // proxy ugi for user via remote user
+ request = getMockRequest(realUser, null, user);
+ ugi = JspHelper.getUGI(context, request, conf);
+ Assert.assertNotNull(ugi.getRealUser());
+ Assert.assertEquals(ugi.getRealUser().getShortUserName(), realUser);
+ Assert.assertEquals(ugi.getShortUserName(), user);
+ checkUgiFromAuth(ugi);
+
+ // proxy ugi for user vi a remote user = real user
+ request = getMockRequest(realUser, realUser, user);
+ ugi = JspHelper.getUGI(context, request, conf);
+ Assert.assertNotNull(ugi.getRealUser());
+ Assert.assertEquals(ugi.getRealUser().getShortUserName(), realUser);
+ Assert.assertEquals(ugi.getShortUserName(), user);
+ checkUgiFromAuth(ugi);
+
+ // proxy ugi for user via remote user != real user
+ request = getMockRequest(realUser, user, user);
+ try {
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad request allowed");
+ } catch (IOException ioe) {
+ Assert.assertEquals(
+ "Usernames not matched: name="+user+" != expected="+realUser,
+ ioe.getMessage());
+ }
+
+ // try to get get a proxy user with unauthorized user
+ try {
+ request = getMockRequest(user, null, realUser);
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad proxy request allowed");
+ } catch (AuthorizationException ae) {
+ Assert.assertEquals(
+ "User: " + user + " is not allowed to impersonate " + realUser,
+ ae.getMessage());
+ }
+ try {
+ request = getMockRequest(user, user, realUser);
+ JspHelper.getUGI(context, request, conf);
+ Assert.fail("bad proxy request allowed");
+ } catch (AuthorizationException ae) {
+ Assert.assertEquals(
+ "User: " + user + " is not allowed to impersonate " + realUser,
+ ae.getMessage());
+ }
+ }
+
+ private HttpServletRequest getMockRequest(String remoteUser, String user, String doAs) {
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ when(request.getParameter(UserParam.NAME)).thenReturn(user);
+ if (doAs != null) {
+ when(request.getParameter(DoAsParam.NAME)).thenReturn(doAs);
+ }
+ when(request.getRemoteUser()).thenReturn(remoteUser);
+ return request;
+ }
+
+ private void checkUgiFromAuth(UserGroupInformation ugi) {
+ if (ugi.getRealUser() != null) {
+ Assert.assertEquals(AuthenticationMethod.PROXY,
+ ugi.getAuthenticationMethod());
+ Assert.assertEquals(AuthenticationMethod.KERBEROS_SSL,
+ ugi.getRealUser().getAuthenticationMethod());
+ } else {
+ Assert.assertEquals(AuthenticationMethod.KERBEROS_SSL,
+ ugi.getAuthenticationMethod());
+ }
+ }
+
+ private void checkUgiFromToken(UserGroupInformation ugi) {
+ if (ugi.getRealUser() != null) {
+ Assert.assertEquals(AuthenticationMethod.PROXY,
+ ugi.getAuthenticationMethod());
+ Assert.assertEquals(AuthenticationMethod.TOKEN,
+ ugi.getRealUser().getAuthenticationMethod());
+ } else {
+ Assert.assertEquals(AuthenticationMethod.TOKEN,
+ ugi.getAuthenticationMethod());
+ }
+ }
}
From 0c4e670eff6a062561b254ae2b62f3900b37b37b Mon Sep 17 00:00:00 2001
From: Suresh Srinivas
Date: Fri, 27 Jul 2012 21:51:07 +0000
Subject: [PATCH 30/39] HDFS-3679. fuse_dfs notrash option sets usetrash.
Contributed by Conrad Meyer.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1366545 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 5 ++++-
.../hadoop-hdfs/src/main/native/fuse-dfs/fuse_options.c | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 4c7d679ad67..1f9ce6f58d3 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -305,7 +305,8 @@ Branch-2 ( Unreleased changes )
HDFS-3613. GSet prints some INFO level values, which aren't
really very useful to all (Andrew Wang via harsh)
- HDFS-3611. NameNode prints unnecessary WARNs about edit log normally skipping a few bytes. (Colin Patrick McCabe via harsh)
+ HDFS-3611. NameNode prints unnecessary WARNs about edit log normally skipping
+ a few bytes. (Colin Patrick McCabe via harsh)
HDFS-3582. Hook daemon process exit for testing. (eli)
@@ -538,6 +539,8 @@ Branch-2 ( Unreleased changes )
HDFS-3626. Creating file with invalid path can corrupt edit log (todd)
+ HDFS-3679. fuse_dfs notrash option sets usetrash. (Conrad Meyer via suresh)
+
BREAKDOWN OF HDFS-3042 SUBTASKS
HDFS-2185. HDFS portion of ZK-based FailoverController (todd)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_options.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_options.c
index 35829743041..8461ce40f91 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_options.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_options.c
@@ -124,7 +124,7 @@ int dfs_options(void *data, const char *arg, int key, struct fuse_args *outargs
options.usetrash = 1;
break;
case KEY_NOTRASH:
- options.usetrash = 1;
+ options.usetrash = 0;
break;
case KEY_RO:
options.read_only = 1;
From e4eec269d91ae541a321ae2f28ff03310682b3fe Mon Sep 17 00:00:00 2001
From: Tsz-wo Sze
Date: Sat, 28 Jul 2012 05:57:47 +0000
Subject: [PATCH 31/39] HDFS-3667. Add retry support to WebHdfsFileSystem.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1366601 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 2 +
.../hadoop/hdfs/ByteRangeInputStream.java | 18 +-
.../apache/hadoop/hdfs/HftpFileSystem.java | 29 +-
.../apache/hadoop/hdfs/NameNodeProxies.java | 11 +-
.../hadoop/hdfs/web/WebHdfsFileSystem.java | 316 ++++++++++++------
.../hdfs/web/resources/DeleteOpParam.java | 5 +
.../hadoop/hdfs/web/resources/GetOpParam.java | 29 +-
.../hdfs/web/resources/HttpOpParam.java | 34 +-
.../hdfs/web/resources/PostOpParam.java | 5 +
.../hadoop/hdfs/web/resources/PutOpParam.java | 13 +-
.../hadoop/hdfs/TestDFSClientRetries.java | 60 +++-
.../TestDelegationTokenForProxyUser.java | 9 +-
.../hdfs/web/TestOffsetUrlInputStream.java | 73 ----
.../hadoop/hdfs/web/TestWebHdfsRetries.java | 37 ++
.../hadoop/hdfs/web/WebHdfsTestUtil.java | 10 +-
15 files changed, 396 insertions(+), 255 deletions(-)
create mode 100644 hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHdfsRetries.java
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 1f9ce6f58d3..5b5a503c8f5 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -373,6 +373,8 @@ Branch-2 ( Unreleased changes )
HDFS-3697. Enable fadvise readahead by default. (todd)
+ HDFS-3667. Add retry support to WebHdfsFileSystem. (szetszwo)
+
BUG FIXES
HDFS-3385. The last block of INodeFileUnderConstruction is not
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/ByteRangeInputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/ByteRangeInputStream.java
index 62c67f9e9d0..e745714c9be 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/ByteRangeInputStream.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/ByteRangeInputStream.java
@@ -57,9 +57,9 @@ public abstract class ByteRangeInputStream extends FSInputStream {
return url;
}
- protected abstract HttpURLConnection openConnection() throws IOException;
-
- protected abstract HttpURLConnection openConnection(final long offset) throws IOException;
+ /** Connect to server with a data offset. */
+ protected abstract HttpURLConnection connect(final long offset,
+ final boolean resolved) throws IOException;
}
enum StreamStatus {
@@ -85,9 +85,6 @@ public abstract class ByteRangeInputStream extends FSInputStream {
this.resolvedURL = r;
}
- protected abstract void checkResponseCode(final HttpURLConnection connection
- ) throws IOException;
-
protected abstract URL getResolvedUrl(final HttpURLConnection connection
) throws IOException;
@@ -113,13 +110,10 @@ public abstract class ByteRangeInputStream extends FSInputStream {
protected InputStream openInputStream() throws IOException {
// Use the original url if no resolved url exists, eg. if
// it's the first time a request is made.
- final URLOpener opener =
- (resolvedURL.getURL() == null) ? originalURL : resolvedURL;
-
- final HttpURLConnection connection = opener.openConnection(startPos);
- connection.connect();
- checkResponseCode(connection);
+ final boolean resolved = resolvedURL.getURL() != null;
+ final URLOpener opener = resolved? resolvedURL: originalURL;
+ final HttpURLConnection connection = opener.connect(startPos, resolved);
final String cl = connection.getHeaderField(StreamFile.CONTENT_LENGTH);
if (cl == null) {
throw new IOException(StreamFile.CONTENT_LENGTH+" header is missing");
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/HftpFileSystem.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/HftpFileSystem.java
index 6df5f2dd9b0..8c73e2a6bee 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/HftpFileSystem.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/HftpFileSystem.java
@@ -342,19 +342,28 @@ public class HftpFileSystem extends FileSystem
super(url);
}
- @Override
protected HttpURLConnection openConnection() throws IOException {
return (HttpURLConnection)URLUtils.openConnection(url);
}
/** Use HTTP Range header for specifying offset. */
@Override
- protected HttpURLConnection openConnection(final long offset) throws IOException {
+ protected HttpURLConnection connect(final long offset,
+ final boolean resolved) throws IOException {
final HttpURLConnection conn = openConnection();
conn.setRequestMethod("GET");
if (offset != 0L) {
conn.setRequestProperty("Range", "bytes=" + offset + "-");
}
+ conn.connect();
+
+ //Expects HTTP_OK or HTTP_PARTIAL response codes.
+ final int code = conn.getResponseCode();
+ if (offset != 0L && code != HttpURLConnection.HTTP_PARTIAL) {
+ throw new IOException("HTTP_PARTIAL expected, received " + code);
+ } else if (offset == 0L && code != HttpURLConnection.HTTP_OK) {
+ throw new IOException("HTTP_OK expected, received " + code);
+ }
return conn;
}
}
@@ -368,22 +377,6 @@ public class HftpFileSystem extends FileSystem
this(new RangeHeaderUrlOpener(url), new RangeHeaderUrlOpener(null));
}
- /** Expects HTTP_OK and HTTP_PARTIAL response codes. */
- @Override
- protected void checkResponseCode(final HttpURLConnection connection
- ) throws IOException {
- final int code = connection.getResponseCode();
- if (startPos != 0 && code != HttpURLConnection.HTTP_PARTIAL) {
- // We asked for a byte range but did not receive a partial content
- // response...
- throw new IOException("HTTP_PARTIAL expected, received " + code);
- } else if (startPos == 0 && code != HttpURLConnection.HTTP_OK) {
- // We asked for all bytes from the beginning but didn't receive a 200
- // response (none of the other 2xx codes are valid here)
- throw new IOException("HTTP_OK expected, received " + code);
- }
- }
-
@Override
protected URL getResolvedUrl(final HttpURLConnection connection) {
return connection.getURL();
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/NameNodeProxies.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/NameNodeProxies.java
index cc6517daa52..12ec985fb25 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/NameNodeProxies.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/NameNodeProxies.java
@@ -259,7 +259,7 @@ public class NameNodeProxies {
*
* Note that dfs.client.retry.max < 0 is not allowed.
*/
- private static RetryPolicy getDefaultRpcRetryPolicy(Configuration conf) {
+ public static RetryPolicy getDefaultRetryPolicy(Configuration conf) {
final RetryPolicy multipleLinearRandomRetry = getMultipleLinearRandomRetry(conf);
if (LOG.isDebugEnabled()) {
LOG.debug("multipleLinearRandomRetry = " + multipleLinearRandomRetry);
@@ -300,6 +300,13 @@ public class NameNodeProxies {
+ p.getClass().getSimpleName() + ", exception=" + e);
return p.shouldRetry(e, retries, failovers, isMethodIdempotent);
}
+
+ @Override
+ public String toString() {
+ return "RetryPolicy[" + multipleLinearRandomRetry + ", "
+ + RetryPolicies.TRY_ONCE_THEN_FAIL.getClass().getSimpleName()
+ + "]";
+ }
};
}
}
@@ -335,7 +342,7 @@ public class NameNodeProxies {
boolean withRetries) throws IOException {
RPC.setProtocolEngine(conf, ClientNamenodeProtocolPB.class, ProtobufRpcEngine.class);
- final RetryPolicy defaultPolicy = getDefaultRpcRetryPolicy(conf);
+ final RetryPolicy defaultPolicy = getDefaultRetryPolicy(conf);
final long version = RPC.getProtocolVersion(ClientNamenodeProtocolPB.class);
ClientNamenodeProtocolPB proxy = RPC.getProtocolProxy(
ClientNamenodeProtocolPB.class, version, address, ugi, conf,
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/WebHdfsFileSystem.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/WebHdfsFileSystem.java
index 420d74d5829..44e70e8636a 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/WebHdfsFileSystem.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/WebHdfsFileSystem.java
@@ -55,6 +55,7 @@ import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.ByteRangeInputStream;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.DFSUtil;
+import org.apache.hadoop.hdfs.NameNodeProxies;
import org.apache.hadoop.hdfs.protocol.DSQuotaExceededException;
import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
import org.apache.hadoop.hdfs.protocol.NSQuotaExceededException;
@@ -88,6 +89,7 @@ import org.apache.hadoop.hdfs.web.resources.ReplicationParam;
import org.apache.hadoop.hdfs.web.resources.TokenArgumentParam;
import org.apache.hadoop.hdfs.web.resources.UserParam;
import org.apache.hadoop.io.Text;
+import org.apache.hadoop.io.retry.RetryPolicy;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.AccessControlException;
@@ -147,6 +149,7 @@ public class WebHdfsFileSystem extends FileSystem
private URI uri;
private Token> delegationToken;
private final AuthenticatedURL.Token authToken = new AuthenticatedURL.Token();
+ private RetryPolicy retryPolicy = null;
private Path workingDir;
{
@@ -179,6 +182,7 @@ public class WebHdfsFileSystem extends FileSystem
throw new IllegalArgumentException(e);
}
this.nnAddr = NetUtils.createSocketAddr(uri.getAuthority(), getDefaultPort());
+ this.retryPolicy = NameNodeProxies.getDefaultRetryPolicy(conf);
this.workingDir = getHomeDirectory();
if (UserGroupInformation.isSecurityEnabled()) {
@@ -276,13 +280,13 @@ public class WebHdfsFileSystem extends FileSystem
}
private static Map, ?> validateResponse(final HttpOpParam.Op op,
- final HttpURLConnection conn) throws IOException {
+ final HttpURLConnection conn, boolean unwrapException) throws IOException {
final int code = conn.getResponseCode();
if (code != op.getExpectedHttpResponseCode()) {
final Map, ?> m;
try {
m = jsonParse(conn, true);
- } catch(IOException e) {
+ } catch(Exception e) {
throw new IOException("Unexpected HTTP response: code=" + code + " != "
+ op.getExpectedHttpResponseCode() + ", " + op.toQueryString()
+ ", message=" + conn.getResponseMessage(), e);
@@ -293,21 +297,42 @@ public class WebHdfsFileSystem extends FileSystem
}
final RemoteException re = JsonUtil.toRemoteException(m);
- throw re.unwrapRemoteException(AccessControlException.class,
- InvalidToken.class,
- AuthenticationException.class,
- AuthorizationException.class,
- FileAlreadyExistsException.class,
- FileNotFoundException.class,
- ParentNotDirectoryException.class,
- UnresolvedPathException.class,
- SafeModeException.class,
- DSQuotaExceededException.class,
- NSQuotaExceededException.class);
+ throw unwrapException? toIOException(re): re;
}
return null;
}
+ /**
+ * Covert an exception to an IOException.
+ *
+ * For a non-IOException, wrap it with IOException.
+ * For a RemoteException, unwrap it.
+ * For an IOException which is not a RemoteException, return it.
+ */
+ private static IOException toIOException(Exception e) {
+ if (!(e instanceof IOException)) {
+ return new IOException(e);
+ }
+
+ final IOException ioe = (IOException)e;
+ if (!(ioe instanceof RemoteException)) {
+ return ioe;
+ }
+
+ final RemoteException re = (RemoteException)ioe;
+ return re.unwrapRemoteException(AccessControlException.class,
+ InvalidToken.class,
+ AuthenticationException.class,
+ AuthorizationException.class,
+ FileAlreadyExistsException.class,
+ FileNotFoundException.class,
+ ParentNotDirectoryException.class,
+ UnresolvedPathException.class,
+ SafeModeException.class,
+ DSQuotaExceededException.class,
+ NSQuotaExceededException.class);
+ }
+
/**
* Return a URL pointing to given path on the namenode.
*
@@ -362,70 +387,15 @@ public class WebHdfsFileSystem extends FileSystem
}
private HttpURLConnection getHttpUrlConnection(URL url)
- throws IOException {
+ throws IOException, AuthenticationException {
final HttpURLConnection conn;
- try {
- if (ugi.hasKerberosCredentials()) {
- conn = new AuthenticatedURL(AUTH).openConnection(url, authToken);
- } else {
- conn = (HttpURLConnection)url.openConnection();
- }
- } catch (AuthenticationException e) {
- throw new IOException("Authentication failed, url=" + url, e);
+ if (ugi.hasKerberosCredentials()) {
+ conn = new AuthenticatedURL(AUTH).openConnection(url, authToken);
+ } else {
+ conn = (HttpURLConnection)url.openConnection();
}
return conn;
}
-
- private HttpURLConnection httpConnect(final HttpOpParam.Op op, final Path fspath,
- final Param,?>... parameters) throws IOException {
- final URL url = toUrl(op, fspath, parameters);
-
- //connect and get response
- HttpURLConnection conn = getHttpUrlConnection(url);
- try {
- conn.setRequestMethod(op.getType().toString());
- if (op.getDoOutput()) {
- conn = twoStepWrite(conn, op);
- conn.setRequestProperty("Content-Type", "application/octet-stream");
- }
- conn.setDoOutput(op.getDoOutput());
- conn.connect();
- return conn;
- } catch (IOException e) {
- conn.disconnect();
- throw e;
- }
- }
-
- /**
- * Two-step Create/Append:
- * Step 1) Submit a Http request with neither auto-redirect nor data.
- * Step 2) Submit another Http request with the URL from the Location header with data.
- *
- * The reason of having two-step create/append is for preventing clients to
- * send out the data before the redirect. This issue is addressed by the
- * "Expect: 100-continue" header in HTTP/1.1; see RFC 2616, Section 8.2.3.
- * Unfortunately, there are software library bugs (e.g. Jetty 6 http server
- * and Java 6 http client), which do not correctly implement "Expect:
- * 100-continue". The two-step create/append is a temporary workaround for
- * the software library bugs.
- */
- static HttpURLConnection twoStepWrite(HttpURLConnection conn,
- final HttpOpParam.Op op) throws IOException {
- //Step 1) Submit a Http request with neither auto-redirect nor data.
- conn.setInstanceFollowRedirects(false);
- conn.setDoOutput(false);
- conn.connect();
- validateResponse(HttpOpParam.TemporaryRedirectOp.valueOf(op), conn);
- final String redirect = conn.getHeaderField("Location");
- conn.disconnect();
-
- //Step 2) Submit another Http request with the URL from the Location header with data.
- conn = (HttpURLConnection)new URL(redirect).openConnection();
- conn.setRequestMethod(op.getType().toString());
- conn.setChunkedStreamingMode(32 << 10); //32kB-chunk
- return conn;
- }
/**
* Run a http operation.
@@ -439,12 +409,158 @@ public class WebHdfsFileSystem extends FileSystem
*/
private Map, ?> run(final HttpOpParam.Op op, final Path fspath,
final Param,?>... parameters) throws IOException {
- final HttpURLConnection conn = httpConnect(op, fspath, parameters);
- try {
- final Map, ?> m = validateResponse(op, conn);
- return m != null? m: jsonParse(conn, false);
- } finally {
- conn.disconnect();
+ return new Runner(op, fspath, parameters).run().json;
+ }
+
+ /**
+ * This class is for initialing a HTTP connection, connecting to server,
+ * obtaining a response, and also handling retry on failures.
+ */
+ class Runner {
+ private final HttpOpParam.Op op;
+ private final URL url;
+ private final boolean redirected;
+
+ private boolean checkRetry;
+ private HttpURLConnection conn = null;
+ private Map, ?> json = null;
+
+ Runner(final HttpOpParam.Op op, final URL url, final boolean redirected) {
+ this.op = op;
+ this.url = url;
+ this.redirected = redirected;
+ }
+
+ Runner(final HttpOpParam.Op op, final Path fspath,
+ final Param,?>... parameters) throws IOException {
+ this(op, toUrl(op, fspath, parameters), false);
+ }
+
+ Runner(final HttpOpParam.Op op, final HttpURLConnection conn) {
+ this(op, null, false);
+ this.conn = conn;
+ }
+
+ private void init() throws IOException {
+ checkRetry = !redirected;
+ try {
+ conn = getHttpUrlConnection(url);
+ } catch(AuthenticationException ae) {
+ checkRetry = false;
+ throw new IOException("Authentication failed, url=" + url, ae);
+ }
+ }
+
+ private void connect() throws IOException {
+ connect(op.getDoOutput());
+ }
+
+ private void connect(boolean doOutput) throws IOException {
+ conn.setRequestMethod(op.getType().toString());
+ conn.setDoOutput(doOutput);
+ conn.setInstanceFollowRedirects(false);
+ conn.connect();
+ }
+
+ private void disconnect() {
+ if (conn != null) {
+ conn.disconnect();
+ conn = null;
+ }
+ }
+
+ Runner run() throws IOException {
+ for(int retry = 0; ; retry++) {
+ try {
+ init();
+ if (op.getDoOutput()) {
+ twoStepWrite();
+ } else {
+ getResponse(op != GetOpParam.Op.OPEN);
+ }
+ return this;
+ } catch(IOException ioe) {
+ shouldRetry(ioe, retry);
+ }
+ }
+ }
+
+ private void shouldRetry(final IOException ioe, final int retry
+ ) throws IOException {
+ if (checkRetry) {
+ try {
+ final RetryPolicy.RetryAction a = retryPolicy.shouldRetry(
+ ioe, retry, 0, true);
+ if (a.action == RetryPolicy.RetryAction.RetryDecision.RETRY) {
+ LOG.info("Retrying connect to namenode: " + nnAddr
+ + ". Already tried " + retry + " time(s); retry policy is "
+ + retryPolicy + ", delay " + a.delayMillis + "ms.");
+ Thread.sleep(a.delayMillis);
+ return;
+ }
+ } catch(Exception e) {
+ LOG.warn("Original exception is ", ioe);
+ throw toIOException(e);
+ }
+ }
+ throw toIOException(ioe);
+ }
+
+ /**
+ * Two-step Create/Append:
+ * Step 1) Submit a Http request with neither auto-redirect nor data.
+ * Step 2) Submit another Http request with the URL from the Location header with data.
+ *
+ * The reason of having two-step create/append is for preventing clients to
+ * send out the data before the redirect. This issue is addressed by the
+ * "Expect: 100-continue" header in HTTP/1.1; see RFC 2616, Section 8.2.3.
+ * Unfortunately, there are software library bugs (e.g. Jetty 6 http server
+ * and Java 6 http client), which do not correctly implement "Expect:
+ * 100-continue". The two-step create/append is a temporary workaround for
+ * the software library bugs.
+ */
+ HttpURLConnection twoStepWrite() throws IOException {
+ //Step 1) Submit a Http request with neither auto-redirect nor data.
+ connect(false);
+ validateResponse(HttpOpParam.TemporaryRedirectOp.valueOf(op), conn, false);
+ final String redirect = conn.getHeaderField("Location");
+ disconnect();
+ checkRetry = false;
+
+ //Step 2) Submit another Http request with the URL from the Location header with data.
+ conn = (HttpURLConnection)new URL(redirect).openConnection();
+ conn.setChunkedStreamingMode(32 << 10); //32kB-chunk
+ connect();
+ return conn;
+ }
+
+ FSDataOutputStream write(final int bufferSize) throws IOException {
+ return WebHdfsFileSystem.this.write(op, conn, bufferSize);
+ }
+
+ void getResponse(boolean getJsonAndDisconnect) throws IOException {
+ try {
+ connect();
+ if (!redirected && op.getRedirect()) {
+ final String redirect = conn.getHeaderField("Location");
+ json = validateResponse(HttpOpParam.TemporaryRedirectOp.valueOf(op),
+ conn, false);
+ disconnect();
+
+ checkRetry = false;
+ conn = (HttpURLConnection)new URL(redirect).openConnection();
+ connect();
+ }
+
+ json = validateResponse(op, conn, false);
+ if (json == null && getJsonAndDisconnect) {
+ json = jsonParse(conn, false);
+ }
+ } finally {
+ if (getJsonAndDisconnect) {
+ disconnect();
+ }
+ }
}
}
@@ -578,7 +694,7 @@ public class WebHdfsFileSystem extends FileSystem
super.close();
} finally {
try {
- validateResponse(op, conn);
+ validateResponse(op, conn, true);
} finally {
conn.disconnect();
}
@@ -594,13 +710,14 @@ public class WebHdfsFileSystem extends FileSystem
statistics.incrementWriteOps(1);
final HttpOpParam.Op op = PutOpParam.Op.CREATE;
- final HttpURLConnection conn = httpConnect(op, f,
+ return new Runner(op, f,
new PermissionParam(applyUMask(permission)),
new OverwriteParam(overwrite),
new BufferSizeParam(bufferSize),
new ReplicationParam(replication),
- new BlockSizeParam(blockSize));
- return write(op, conn, bufferSize);
+ new BlockSizeParam(blockSize))
+ .run()
+ .write(bufferSize);
}
@Override
@@ -609,9 +726,9 @@ public class WebHdfsFileSystem extends FileSystem
statistics.incrementWriteOps(1);
final HttpOpParam.Op op = PostOpParam.Op.APPEND;
- final HttpURLConnection conn = httpConnect(op, f,
- new BufferSizeParam(bufferSize));
- return write(op, conn, bufferSize);
+ return new Runner(op, f, new BufferSizeParam(bufferSize))
+ .run()
+ .write(bufferSize);
}
@SuppressWarnings("deprecation")
@@ -638,26 +755,17 @@ public class WebHdfsFileSystem extends FileSystem
}
class OffsetUrlOpener extends ByteRangeInputStream.URLOpener {
- /** The url with offset parameter */
- private URL offsetUrl;
-
OffsetUrlOpener(final URL url) {
super(url);
}
- /** Open connection with offset url. */
+ /** Setup offset url and connect. */
@Override
- protected HttpURLConnection openConnection() throws IOException {
- return getHttpUrlConnection(offsetUrl);
- }
-
- /** Setup offset url before open connection. */
- @Override
- protected HttpURLConnection openConnection(final long offset) throws IOException {
- offsetUrl = offset == 0L? url: new URL(url + "&" + new OffsetParam(offset));
- final HttpURLConnection conn = openConnection();
- conn.setRequestMethod("GET");
- return conn;
+ protected HttpURLConnection connect(final long offset,
+ final boolean resolved) throws IOException {
+ final URL offsetUrl = offset == 0L? url
+ : new URL(url + "&" + new OffsetParam(offset));
+ return new Runner(GetOpParam.Op.OPEN, offsetUrl, resolved).run().conn;
}
}
@@ -698,12 +806,6 @@ public class WebHdfsFileSystem extends FileSystem
OffsetUrlInputStream(OffsetUrlOpener o, OffsetUrlOpener r) {
super(o, r);
}
-
- @Override
- protected void checkResponseCode(final HttpURLConnection connection
- ) throws IOException {
- validateResponse(GetOpParam.Op.OPEN, connection);
- }
/** Remove offset parameter before returning the resolved url. */
@Override
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/DeleteOpParam.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/DeleteOpParam.java
index 12962b4a4ee..a82b8a72c8e 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/DeleteOpParam.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/DeleteOpParam.java
@@ -43,6 +43,11 @@ public class DeleteOpParam extends HttpOpParam {
return false;
}
+ @Override
+ public boolean getRedirect() {
+ return false;
+ }
+
@Override
public int getExpectedHttpResponseCode() {
return expectedHttpResponseCode;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/GetOpParam.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/GetOpParam.java
index 34d6e12ecf9..6bbd9e21693 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/GetOpParam.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/GetOpParam.java
@@ -23,25 +23,27 @@ import java.net.HttpURLConnection;
public class GetOpParam extends HttpOpParam {
/** Get operations. */
public static enum Op implements HttpOpParam.Op {
- OPEN(HttpURLConnection.HTTP_OK),
+ OPEN(true, HttpURLConnection.HTTP_OK),
- GETFILESTATUS(HttpURLConnection.HTTP_OK),
- LISTSTATUS(HttpURLConnection.HTTP_OK),
- GETCONTENTSUMMARY(HttpURLConnection.HTTP_OK),
- GETFILECHECKSUM(HttpURLConnection.HTTP_OK),
+ GETFILESTATUS(false, HttpURLConnection.HTTP_OK),
+ LISTSTATUS(false, HttpURLConnection.HTTP_OK),
+ GETCONTENTSUMMARY(false, HttpURLConnection.HTTP_OK),
+ GETFILECHECKSUM(true, HttpURLConnection.HTTP_OK),
- GETHOMEDIRECTORY(HttpURLConnection.HTTP_OK),
- GETDELEGATIONTOKEN(HttpURLConnection.HTTP_OK),
- GETDELEGATIONTOKENS(HttpURLConnection.HTTP_OK),
+ GETHOMEDIRECTORY(false, HttpURLConnection.HTTP_OK),
+ GETDELEGATIONTOKEN(false, HttpURLConnection.HTTP_OK),
+ GETDELEGATIONTOKENS(false, HttpURLConnection.HTTP_OK),
/** GET_BLOCK_LOCATIONS is a private unstable op. */
- GET_BLOCK_LOCATIONS(HttpURLConnection.HTTP_OK),
+ GET_BLOCK_LOCATIONS(false, HttpURLConnection.HTTP_OK),
- NULL(HttpURLConnection.HTTP_NOT_IMPLEMENTED);
+ NULL(false, HttpURLConnection.HTTP_NOT_IMPLEMENTED);
+ final boolean redirect;
final int expectedHttpResponseCode;
- Op(final int expectedHttpResponseCode) {
+ Op(final boolean redirect, final int expectedHttpResponseCode) {
+ this.redirect = redirect;
this.expectedHttpResponseCode = expectedHttpResponseCode;
}
@@ -55,6 +57,11 @@ public class GetOpParam extends HttpOpParam {
return false;
}
+ @Override
+ public boolean getRedirect() {
+ return redirect;
+ }
+
@Override
public int getExpectedHttpResponseCode() {
return expectedHttpResponseCode;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/HttpOpParam.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/HttpOpParam.java
index ab32ab59aa3..9765f67996c 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/HttpOpParam.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/HttpOpParam.java
@@ -17,6 +17,10 @@
*/
package org.apache.hadoop.hdfs.web.resources;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
import javax.ws.rs.core.Response;
@@ -42,6 +46,9 @@ public abstract class HttpOpParam & HttpOpParam.Op>
/** @return true if the operation will do output. */
public boolean getDoOutput();
+ /** @return true if the operation will be redirected. */
+ public boolean getRedirect();
+
/** @return true the expected http response code. */
public int getExpectedHttpResponseCode();
@@ -51,15 +58,25 @@ public abstract class HttpOpParam & HttpOpParam.Op>
/** Expects HTTP response 307 "Temporary Redirect". */
public static class TemporaryRedirectOp implements Op {
- static final TemporaryRedirectOp CREATE = new TemporaryRedirectOp(PutOpParam.Op.CREATE);
- static final TemporaryRedirectOp APPEND = new TemporaryRedirectOp(PostOpParam.Op.APPEND);
+ static final TemporaryRedirectOp CREATE = new TemporaryRedirectOp(
+ PutOpParam.Op.CREATE);
+ static final TemporaryRedirectOp APPEND = new TemporaryRedirectOp(
+ PostOpParam.Op.APPEND);
+ static final TemporaryRedirectOp OPEN = new TemporaryRedirectOp(
+ GetOpParam.Op.OPEN);
+ static final TemporaryRedirectOp GETFILECHECKSUM = new TemporaryRedirectOp(
+ GetOpParam.Op.GETFILECHECKSUM);
+ static final List values
+ = Collections.unmodifiableList(Arrays.asList(
+ new TemporaryRedirectOp[]{CREATE, APPEND, OPEN, GETFILECHECKSUM}));
+
/** Get an object for the given op. */
public static TemporaryRedirectOp valueOf(final Op op) {
- if (op == CREATE.op) {
- return CREATE;
- } else if (op == APPEND.op) {
- return APPEND;
+ for(TemporaryRedirectOp t : values) {
+ if (op == t.op) {
+ return t;
+ }
}
throw new IllegalArgumentException(op + " not found.");
}
@@ -80,6 +97,11 @@ public abstract class HttpOpParam & HttpOpParam.Op>
return op.getDoOutput();
}
+ @Override
+ public boolean getRedirect() {
+ return false;
+ }
+
/** Override the original expected response with "Temporary Redirect". */
@Override
public int getExpectedHttpResponseCode() {
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/PostOpParam.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/PostOpParam.java
index 89c02fe0d79..83f2cd19155 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/PostOpParam.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/PostOpParam.java
@@ -43,6 +43,11 @@ public class PostOpParam extends HttpOpParam {
return true;
}
+ @Override
+ public boolean getRedirect() {
+ return true;
+ }
+
@Override
public int getExpectedHttpResponseCode() {
return expectedHttpResponseCode;
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/PutOpParam.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/PutOpParam.java
index b6fc9198801..77bad214225 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/PutOpParam.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/web/resources/PutOpParam.java
@@ -39,11 +39,11 @@ public class PutOpParam extends HttpOpParam {
NULL(false, HttpURLConnection.HTTP_NOT_IMPLEMENTED);
- final boolean doOutput;
+ final boolean doOutputAndRedirect;
final int expectedHttpResponseCode;
- Op(final boolean doOutput, final int expectedHttpResponseCode) {
- this.doOutput = doOutput;
+ Op(final boolean doOutputAndRedirect, final int expectedHttpResponseCode) {
+ this.doOutputAndRedirect = doOutputAndRedirect;
this.expectedHttpResponseCode = expectedHttpResponseCode;
}
@@ -54,7 +54,12 @@ public class PutOpParam extends HttpOpParam {
@Override
public boolean getDoOutput() {
- return doOutput;
+ return doOutputAndRedirect;
+ }
+
+ @Override
+ public boolean getRedirect() {
+ return doOutputAndRedirect;
}
@Override
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSClientRetries.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSClientRetries.java
index 5c5de9db5ec..03ba1e96ba5 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSClientRetries.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSClientRetries.java
@@ -47,6 +47,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.impl.Log4JLogger;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeys;
+import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileChecksum;
import org.apache.hadoop.fs.FileStatus;
@@ -66,6 +67,7 @@ import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
import org.apache.hadoop.hdfs.server.namenode.NameNode;
import org.apache.hadoop.hdfs.server.namenode.NotReplicatedYetException;
import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocols;
+import org.apache.hadoop.hdfs.web.WebHdfsTestUtil;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Writable;
@@ -74,6 +76,7 @@ import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.ipc.Server;
import org.apache.hadoop.net.NetUtils;
+import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.Time;
import org.apache.log4j.Level;
@@ -825,13 +828,17 @@ public class TestDFSClientRetries {
/** Test client retry with namenode restarting. */
@Test
public void testNamenodeRestart() throws Exception {
+ namenodeRestartTest(new Configuration(), false);
+ }
+
+ public static void namenodeRestartTest(final Configuration conf,
+ final boolean isWebHDFS) throws Exception {
((Log4JLogger)DFSClient.LOG).getLogger().setLevel(Level.ALL);
final List exceptions = new ArrayList();
final Path dir = new Path("/testNamenodeRestart");
- final Configuration conf = new Configuration();
conf.setBoolean(DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_ENABLED_KEY, true);
final short numDatanodes = 3;
@@ -841,16 +848,18 @@ public class TestDFSClientRetries {
try {
cluster.waitActive();
final DistributedFileSystem dfs = cluster.getFileSystem();
+ final FileSystem fs = isWebHDFS?
+ WebHdfsTestUtil.getWebHdfsFileSystem(conf): dfs;
final URI uri = dfs.getUri();
assertTrue(HdfsUtils.isHealthy(uri));
//create a file
final long length = 1L << 20;
final Path file1 = new Path(dir, "foo");
- DFSTestUtil.createFile(dfs, file1, length, numDatanodes, 20120406L);
+ DFSTestUtil.createFile(fs, file1, length, numDatanodes, 20120406L);
//get file status
- final FileStatus s1 = dfs.getFileStatus(file1);
+ final FileStatus s1 = fs.getFileStatus(file1);
assertEquals(length, s1.getLen());
//shutdown namenode
@@ -858,6 +867,25 @@ public class TestDFSClientRetries {
cluster.shutdownNameNode(0);
assertFalse(HdfsUtils.isHealthy(uri));
+ //namenode is down, read the file in a thread
+ final Thread reader = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ //it should retry till namenode is up.
+ final FileSystem fs = createFsWithDifferentUsername(conf, isWebHDFS);
+ final FSDataInputStream in = fs.open(file1);
+ int count = 0;
+ for(; in.read() != -1; count++);
+ in.close();
+ assertEquals(s1.getLen(), count);
+ } catch (Exception e) {
+ exceptions.add(e);
+ }
+ }
+ });
+ reader.start();
+
//namenode is down, create another file in a thread
final Path file3 = new Path(dir, "file");
final Thread thread = new Thread(new Runnable() {
@@ -865,7 +893,7 @@ public class TestDFSClientRetries {
public void run() {
try {
//it should retry till namenode is up.
- final FileSystem fs = AppendTestUtil.createHdfsWithDifferentUsername(conf);
+ final FileSystem fs = createFsWithDifferentUsername(conf, isWebHDFS);
DFSTestUtil.createFile(fs, file3, length, numDatanodes, 20120406L);
} catch (Exception e) {
exceptions.add(e);
@@ -892,12 +920,15 @@ public class TestDFSClientRetries {
}).start();
//namenode is down, it should retry until namenode is up again.
- final FileStatus s2 = dfs.getFileStatus(file1);
+ final FileStatus s2 = fs.getFileStatus(file1);
assertEquals(s1, s2);
//check file1 and file3
thread.join();
- assertEquals(dfs.getFileChecksum(file1), dfs.getFileChecksum(file3));
+ assertEquals(s1.getLen(), fs.getFileStatus(file3).getLen());
+ assertEquals(fs.getFileChecksum(file1), fs.getFileChecksum(file3));
+
+ reader.join();
//enter safe mode
assertTrue(HdfsUtils.isHealthy(uri));
@@ -922,8 +953,8 @@ public class TestDFSClientRetries {
//namenode is in safe mode, create should retry until it leaves safe mode.
final Path file2 = new Path(dir, "bar");
- DFSTestUtil.createFile(dfs, file2, length, numDatanodes, 20120406L);
- assertEquals(dfs.getFileChecksum(file1), dfs.getFileChecksum(file2));
+ DFSTestUtil.createFile(fs, file2, length, numDatanodes, 20120406L);
+ assertEquals(fs.getFileChecksum(file1), fs.getFileChecksum(file2));
assertTrue(HdfsUtils.isHealthy(uri));
@@ -931,7 +962,7 @@ public class TestDFSClientRetries {
final Path nonExisting = new Path(dir, "nonExisting");
LOG.info("setPermission: " + nonExisting);
try {
- dfs.setPermission(nonExisting, new FsPermission((short)0));
+ fs.setPermission(nonExisting, new FsPermission((short)0));
fail();
} catch(FileNotFoundException fnfe) {
LOG.info("GOOD!", fnfe);
@@ -949,6 +980,17 @@ public class TestDFSClientRetries {
}
}
+ public static FileSystem createFsWithDifferentUsername(
+ final Configuration conf, final boolean isWebHDFS
+ ) throws IOException, InterruptedException {
+ String username = UserGroupInformation.getCurrentUser().getShortUserName()+"_XXX";
+ UserGroupInformation ugi =
+ UserGroupInformation.createUserForTesting(username, new String[]{"supergroup"});
+
+ return isWebHDFS? WebHdfsTestUtil.getWebHdfsFileSystemAs(ugi, conf)
+ : DFSTestUtil.getFileSystemAs(ugi, conf);
+ }
+
@Test
public void testMultipleLinearRandomRetry() {
parseMultipleLinearRandomRetry(null, "");
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/security/TestDelegationTokenForProxyUser.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/security/TestDelegationTokenForProxyUser.java
index 02c04141050..fe32fcdfbe5 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/security/TestDelegationTokenForProxyUser.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/security/TestDelegationTokenForProxyUser.java
@@ -44,7 +44,6 @@ import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.DFSConfigKeys;
-import org.apache.hadoop.hdfs.DistributedFileSystem;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
@@ -140,9 +139,7 @@ public class TestDelegationTokenForProxyUser {
.doAs(new PrivilegedExceptionAction>() {
@Override
public Token run() throws IOException {
- DistributedFileSystem dfs = (DistributedFileSystem) cluster
- .getFileSystem();
- return dfs.getDelegationToken("RenewerUser");
+ return cluster.getFileSystem().getDelegationToken("RenewerUser");
}
});
DelegationTokenIdentifier identifier = new DelegationTokenIdentifier();
@@ -206,7 +203,7 @@ public class TestDelegationTokenForProxyUser {
final PutOpParam.Op op = PutOpParam.Op.CREATE;
final URL url = WebHdfsTestUtil.toUrl(webhdfs, op, f, new DoAsParam(PROXY_USER));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn = WebHdfsTestUtil.twoStepWrite(conn, op);
+ conn = WebHdfsTestUtil.twoStepWrite(webhdfs, op, conn);
final FSDataOutputStream out = WebHdfsTestUtil.write(webhdfs, op, conn, 4096);
out.write("Hello, webhdfs user!".getBytes());
out.close();
@@ -221,7 +218,7 @@ public class TestDelegationTokenForProxyUser {
final PostOpParam.Op op = PostOpParam.Op.APPEND;
final URL url = WebHdfsTestUtil.toUrl(webhdfs, op, f, new DoAsParam(PROXY_USER));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn = WebHdfsTestUtil.twoStepWrite(conn, op);
+ conn = WebHdfsTestUtil.twoStepWrite(webhdfs, op, conn);
final FSDataOutputStream out = WebHdfsTestUtil.write(webhdfs, op, conn, 4096);
out.write("\nHello again!".getBytes());
out.close();
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestOffsetUrlInputStream.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestOffsetUrlInputStream.java
index 4ef0dd680e7..1a95fc8c327 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestOffsetUrlInputStream.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestOffsetUrlInputStream.java
@@ -18,22 +18,10 @@
package org.apache.hadoop.hdfs.web;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
import java.io.IOException;
-import java.net.URI;
import java.net.URL;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.hdfs.TestByteRangeInputStream.MockHttpURLConnection;
-import org.apache.hadoop.hdfs.web.WebHdfsFileSystem.OffsetUrlInputStream;
-import org.apache.hadoop.hdfs.web.WebHdfsFileSystem.OffsetUrlOpener;
import org.junit.Test;
public class TestOffsetUrlInputStream {
@@ -73,65 +61,4 @@ public class TestOffsetUrlInputStream {
WebHdfsFileSystem.removeOffsetParam(new URL(s)).toString());
}
}
-
- @Test
- public void testByteRange() throws Exception {
- final Configuration conf = new Configuration();
- final String uri = WebHdfsFileSystem.SCHEME + "://localhost:50070/";
- final WebHdfsFileSystem webhdfs = (WebHdfsFileSystem)FileSystem.get(new URI(uri), conf);
-
- OffsetUrlOpener ospy = spy(webhdfs.new OffsetUrlOpener(new URL("http://test/")));
- doReturn(new MockHttpURLConnection(ospy.getURL())).when(ospy)
- .openConnection();
- OffsetUrlOpener rspy = spy(webhdfs.new OffsetUrlOpener((URL) null));
- doReturn(new MockHttpURLConnection(rspy.getURL())).when(rspy)
- .openConnection();
- final OffsetUrlInputStream is = new OffsetUrlInputStream(ospy, rspy);
-
- assertEquals("getPos wrong", 0, is.getPos());
-
- is.read();
-
- assertNull("Initial call made incorrectly (Range Check)", ospy
- .openConnection().getRequestProperty("Range"));
-
- assertEquals("getPos should be 1 after reading one byte", 1, is.getPos());
-
- is.read();
-
- assertEquals("getPos should be 2 after reading two bytes", 2, is.getPos());
-
- // No additional connections should have been made (no seek)
-
- rspy.setURL(new URL("http://resolvedurl/"));
-
- is.seek(100);
- is.read();
-
- assertEquals("getPos should be 101 after reading one byte", 101,
- is.getPos());
-
- verify(rspy, times(1)).openConnection();
-
- is.seek(101);
- is.read();
-
- verify(rspy, times(1)).openConnection();
-
- // Seek to 101 should not result in another request"
-
- is.seek(2500);
- is.read();
-
- ((MockHttpURLConnection) rspy.openConnection()).setResponseCode(206);
- is.seek(0);
-
- try {
- is.read();
- fail("Exception should be thrown when 206 response is given "
- + "but 200 is expected");
- } catch (IOException e) {
- WebHdfsFileSystem.LOG.info(e.toString());
- }
- }
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHdfsRetries.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHdfsRetries.java
new file mode 100644
index 00000000000..27ff56d8395
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/TestWebHdfsRetries.java
@@ -0,0 +1,37 @@
+/**
+ * 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.hdfs.web;
+
+import org.apache.commons.logging.impl.Log4JLogger;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdfs.TestDFSClientRetries;
+import org.apache.hadoop.hdfs.server.namenode.web.resources.NamenodeWebHdfsMethods;
+import org.apache.log4j.Level;
+import org.junit.Test;
+
+/** Test WebHdfsFileSystem retry on failures. */
+public class TestWebHdfsRetries {
+ /** Test client retry with namenode restarting. */
+ @Test
+ public void testNamenodeRestart() throws Exception {
+ ((Log4JLogger)NamenodeWebHdfsMethods.LOG).getLogger().setLevel(Level.ALL);
+ final Configuration conf = WebHdfsTestUtil.createConf();
+ TestDFSClientRetries.namenodeRestartTest(conf, true);
+ }
+}
\ No newline at end of file
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/WebHdfsTestUtil.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/WebHdfsTestUtil.java
index 9ae0fb28c21..e51a2524ecd 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/WebHdfsTestUtil.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/web/WebHdfsTestUtil.java
@@ -79,13 +79,9 @@ public class WebHdfsTestUtil {
return WebHdfsFileSystem.jsonParse(conn, false);
}
- public static HttpURLConnection twoStepWrite(HttpURLConnection conn,
- final HttpOpParam.Op op) throws IOException {
- conn.setRequestMethod(op.getType().toString());
- conn = WebHdfsFileSystem.twoStepWrite(conn, op);
- conn.setDoOutput(true);
- conn.connect();
- return conn;
+ public static HttpURLConnection twoStepWrite(final WebHdfsFileSystem webhdfs,
+ final HttpOpParam.Op op, HttpURLConnection conn) throws IOException {
+ return webhdfs.new Runner(op, conn).twoStepWrite();
}
public static FSDataOutputStream write(final WebHdfsFileSystem webhdfs,
From 98f1523b20603f7013baeb697662c3bb37505cc6 Mon Sep 17 00:00:00 2001
From: Robert Joseph Evans
Date: Mon, 30 Jul 2012 15:18:11 +0000
Subject: [PATCH 32/39] HADOOP-8627. FS deleteOnExit may delete the wrong path
(daryn via bobby)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1367114 13f79535-47bb-0310-9956-ffa450edef68
---
.../hadoop-common/CHANGES.txt | 2 +
.../apache/hadoop/fs/FilterFileSystem.java | 17 ---------
.../hadoop/fs/TestFilterFileSystem.java | 4 +-
.../fs/viewfs/TestChRootedFileSystem.java | 37 ++++++++++++++++++-
4 files changed, 41 insertions(+), 19 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index f01b8a5db2e..3d1d755e4b6 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -857,6 +857,8 @@ Release 0.23.3 - UNRELEASED
HADOOP-8613. AbstractDelegationTokenIdentifier#getUser() should set token
auth type. (daryn)
+ HADOOP-8627. FS deleteOnExit may delete the wrong path (daryn via bobby)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
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 6cbaf591e5a..38ddb6c5f58 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
@@ -191,23 +191,6 @@ public class FilterFileSystem extends FileSystem {
return fs.delete(f, recursive);
}
- /**
- * Mark a path to be deleted when FileSystem is closed.
- * When the JVM shuts down,
- * all FileSystem objects will be closed automatically.
- * Then,
- * the marked path will be deleted as a result of closing the FileSystem.
- *
- * The path has to exist in the file system.
- *
- * @param f the path to delete.
- * @return true if deleteOnExit is successful, otherwise false.
- * @throws IOException
- */
- public boolean deleteOnExit(Path f) throws IOException {
- return fs.deleteOnExit(f);
- }
-
/** List files in a directory. */
public FileStatus[] listStatus(Path f) throws IOException {
return fs.listStatus(f);
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFilterFileSystem.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFilterFileSystem.java
index 647a583d103..a374c08a186 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFilterFileSystem.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFilterFileSystem.java
@@ -179,7 +179,9 @@ public class TestFilterFileSystem {
public Token> getDelegationToken(String renewer) throws IOException {
return null;
}
-
+ public boolean deleteOnExit(Path f) throws IOException {
+ return false;
+ }
public String getScheme() {
return "dontcheck";
}
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 127866be1b5..44d7a4a7c13 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
@@ -26,6 +26,7 @@ import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.ContentSummary;
import org.apache.hadoop.fs.FileSystem;
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.viewfs.ChRootedFileSystem;
@@ -33,6 +34,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import static org.mockito.Mockito.*;
public class TestChRootedFileSystem {
FileSystem fSys; // The ChRoootedFs
@@ -314,4 +316,37 @@ public class TestChRootedFileSystem {
public void testResolvePathNonExisting() throws IOException {
fSys.resolvePath(new Path("/nonExisting"));
}
-}
+
+ @Test
+ public void testDeleteOnExitPathHandling() 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();
+
+ // ensure delete propagates the correct path
+ Path chrootPath = new Path("/c");
+ Path rawPath = new Path("/a/b/c");
+ chrootFs.delete(chrootPath, false);
+ verify(mockFs).delete(eq(rawPath), eq(false));
+ reset(mockFs);
+
+ // fake that the path exists for deleteOnExit
+ FileStatus stat = mock(FileStatus.class);
+ when(mockFs.getFileStatus(eq(rawPath))).thenReturn(stat);
+ // ensure deleteOnExit propagates the correct path
+ chrootFs.deleteOnExit(chrootPath);
+ chrootFs.close();
+ verify(mockFs).delete(eq(rawPath), eq(true));
+ }
+
+ static class MockFileSystem extends FilterFileSystem {
+ MockFileSystem() {
+ super(mock(FileSystem.class));
+ }
+ public void initialize(URI name, Configuration conf) throws IOException {}
+ }
+}
\ No newline at end of file
From d8584fde03c4b557ca6de74a0e32e6a4664039d9 Mon Sep 17 00:00:00 2001
From: Robert Joseph Evans
Date: Mon, 30 Jul 2012 18:05:38 +0000
Subject: [PATCH 33/39] HADOOP-8634. Ensure FileSystem#close doesn't squawk for
deleteOnExit paths (daryn via bobby)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1367196 13f79535-47bb-0310-9956-ffa450edef68
---
.../hadoop-common/CHANGES.txt | 3 ++
.../java/org/apache/hadoop/fs/FileSystem.java | 4 ++-
.../hadoop/fs/TestFileSystemCaching.java | 28 +++++++++++++++++--
3 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index 3d1d755e4b6..a2f3ff80e5c 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -859,6 +859,9 @@ Release 0.23.3 - UNRELEASED
HADOOP-8627. FS deleteOnExit may delete the wrong path (daryn via bobby)
+ HADOOP-8634. Ensure FileSystem#close doesn't squawk for deleteOnExit paths
+ (daryn via bobby)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
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 f65d12c711c..42b30c4999b 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
@@ -1224,7 +1224,9 @@ public abstract class FileSystem extends Configured implements Closeable {
for (Iterator iter = deleteOnExit.iterator(); iter.hasNext();) {
Path path = iter.next();
try {
- delete(path, true);
+ if (exists(path)) {
+ delete(path, true);
+ }
}
catch (IOException e) {
LOG.info("Ignoring failure to deleteOnExit for path " + path);
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
index 910139b56ec..e8debf8dfaf 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
@@ -35,7 +35,7 @@ import java.security.PrivilegedExceptionAction;
import java.util.concurrent.Semaphore;
import static org.junit.Assert.*;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.*;
public class TestFileSystemCaching {
@@ -267,4 +267,28 @@ public class TestFileSystemCaching {
});
assertNotSame(fsA, fsA1);
}
-}
+
+ @Test
+ public void testDeleteOnExitChecksExists() throws Exception {
+ FileSystem mockFs = mock(FileSystem.class);
+ FileSystem fs = new FilterFileSystem(mockFs);
+ Path p = new Path("/a");
+
+ // path has to exist for deleteOnExit to register it
+ when(mockFs.getFileStatus(p)).thenReturn(new FileStatus());
+ fs.deleteOnExit(p);
+ verify(mockFs).getFileStatus(eq(p));
+ fs.close();
+ verify(mockFs).delete(eq(p), anyBoolean());
+ reset(mockFs);
+
+ // make sure it doesn't try to delete a file that doesn't exist
+ when(mockFs.getFileStatus(p)).thenReturn(new FileStatus());
+ fs.deleteOnExit(p);
+ verify(mockFs).getFileStatus(eq(p));
+ reset(mockFs);
+ fs.close();
+ verify(mockFs).getFileStatus(eq(p));
+ verify(mockFs, never()).delete(any(Path.class), anyBoolean());
+ }
+}
\ No newline at end of file
From aeade62b95b4a39ea9f534c805fa1c6786a30077 Mon Sep 17 00:00:00 2001
From: Aaron Myers
Date: Mon, 30 Jul 2012 18:07:07 +0000
Subject: [PATCH 34/39] HDFS-3732. fuse_dfs: incorrect configuration value
checked for connection expiry timer period. Contributed by Colin Patrick
McCabe.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1367199 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt | 3 +++
.../hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
index 5b5a503c8f5..05335ff7069 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES.txt
@@ -543,6 +543,9 @@ Branch-2 ( Unreleased changes )
HDFS-3679. fuse_dfs notrash option sets usetrash. (Conrad Meyer via suresh)
+ HDFS-3732. fuse_dfs: incorrect configuration value checked for connection
+ expiry timer period. (Colin Patrick McCabe via atm)
+
BREAKDOWN OF HDFS-3042 SUBTASKS
HDFS-2185. HDFS portion of ZK-based FailoverController (todd)
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c
index c6624fad999..2a39d85263d 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/native/fuse-dfs/fuse_connect.c
@@ -135,7 +135,7 @@ int fuseConnectInit(const char *nnUri, int port)
int ret;
gTimerPeriod = FUSE_CONN_DEFAULT_TIMER_PERIOD;
- ret = hdfsConfGetInt(HADOOP_FUSE_CONNECTION_TIMEOUT, &gTimerPeriod);
+ ret = hdfsConfGetInt(HADOOP_FUSE_TIMER_PERIOD, &gTimerPeriod);
if (ret) {
fprintf(stderr, "Unable to determine the configured value for %s.",
HADOOP_FUSE_TIMER_PERIOD);
From 48a2e2b4e279800b4371e6715e942392aa991ffe Mon Sep 17 00:00:00 2001
From: Robert Joseph Evans
Date: Mon, 30 Jul 2012 18:11:32 +0000
Subject: [PATCH 35/39] HADOOP-8550. hadoop fs -touchz automatically created
parent directories (John George via bobby)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1367202 13f79535-47bb-0310-9956-ffa450edef68
---
.../hadoop-common/CHANGES.txt | 3 +
.../org/apache/hadoop/fs/shell/Touchz.java | 4 +
.../org/apache/hadoop/hdfs/TestDFSShell.java | 13 ++++
.../src/test/resources/testHDFSConf.xml | 77 ++++++++++++++++++-
4 files changed, 95 insertions(+), 2 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index a2f3ff80e5c..d1faef370a6 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -862,6 +862,9 @@ Release 0.23.3 - UNRELEASED
HADOOP-8634. Ensure FileSystem#close doesn't squawk for deleteOnExit paths
(daryn via bobby)
+ HADOOP-8550. hadoop fs -touchz automatically created parent directories
+ (John George via bobby)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Touchz.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Touchz.java
index 18c9aa74c29..9e0844e0f41 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Touchz.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/Touchz.java
@@ -25,6 +25,7 @@ import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.fs.shell.PathExceptions.PathIOException;
import org.apache.hadoop.fs.shell.PathExceptions.PathIsDirectoryException;
+import org.apache.hadoop.fs.shell.PathExceptions.PathNotFoundException;
/**
* Unix touch like commands
@@ -70,6 +71,9 @@ class Touch extends FsCommand {
@Override
protected void processNonexistentPath(PathData item) throws IOException {
+ if (!item.parentExists()) {
+ throw new PathNotFoundException(item.toString());
+ }
touchz(item);
}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java
index ce402b1878b..4387fb75a8c 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestDFSShell.java
@@ -1108,6 +1108,19 @@ public class TestDFSShell {
}
assertEquals(0, val);
+ args = new String[2];
+ args[0] = "-touchz";
+ args[1] = "/test/mkdirs/thisDirNotExists/noFileHere";
+ val = -1;
+ try {
+ val = shell.run(args);
+ } catch (Exception e) {
+ System.err.println("Exception raised from DFSShell.run " +
+ e.getLocalizedMessage());
+ }
+ assertEquals(1, val);
+
+
args = new String[3];
args[0] = "-test";
args[1] = "-e";
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testHDFSConf.xml b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testHDFSConf.xml
index a611c2e3c39..40e3d4f919a 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testHDFSConf.xml
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/testHDFSConf.xml
@@ -55,6 +55,7 @@
ls: file using relative path
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -ls file1
@@ -76,6 +77,7 @@
ls: files using globbing
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -touchz file2
-fs NAMENODE -touchz file3
@@ -937,6 +939,7 @@
-fs NAMENODE -mkdir /dir0
-fs NAMENODE -mkdir /dir0/\*
-fs NAMENODE -touchz /dir0/\*/file
+ -fs NAMENODE -mkdir /dir0/dir1/
-fs NAMENODE -touchz /dir0/dir1/file1
-fs NAMENODE -rm -r /dir0/\*
-fs NAMENODE -ls -R /dir0
@@ -977,7 +980,7 @@
du: file using relative path
- -fs NAMENODE -touchz test
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -put CLITEST_DATA/data15bytes data15bytesZZ
-fs NAMENODE -du data15bytesZZ
@@ -995,6 +998,7 @@
du: files using globbing
+ -fs NAMENODE -mkdir -p CLITEST_DATA
-fs NAMENODE -put CLITEST_DATA/data15bytes data15bytes
-fs NAMENODE -put CLITEST_DATA/data30bytes data30bytes
-fs NAMENODE -put CLITEST_DATA/data60bytes data60bytes
@@ -1724,6 +1728,7 @@
mv: file (relative) to file (relative)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -mv file1 file2
-fs NAMENODE -ls file*
@@ -2127,6 +2132,7 @@
cp: file (absolute path) to file (relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz /file1
-fs NAMENODE -cp /file1 file2
-fs NAMENODE -ls /file1 file2
@@ -2149,6 +2155,7 @@
cp: file (relative path) to file (absolute path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -cp file1 /file2
-fs NAMENODE -ls file1 /file2
@@ -2171,6 +2178,7 @@
cp: file (relative path) to file (relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -cp file1 file2
-fs NAMENODE -ls file1 file2
@@ -2720,6 +2728,7 @@
cp: putting file into an already existing destination with -f option(absolute path)
+ -fs NAMENODE -mkdir /user
-fs NAMENODE -touchz /user/file0
-fs NAMENODE -cp -f CLITEST_DATA/data120bytes /user/file0
-fs NAMENODE -cat /user/file0
@@ -3160,6 +3169,7 @@
rm: removing a file (absolute path)
+ -fs NAMENODE -mkdir /dir0
-fs NAMENODE -touchz /dir0/file0
-fs NAMENODE -rm /dir0/file0
@@ -3177,6 +3187,7 @@
rm: removing a file (relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -rm file0
@@ -3194,6 +3205,7 @@
rm: removing files by globbing (absolute path)
+ -fs NAMENODE -mkdir /dir0
-fs NAMENODE -touchz /dir0/file0
-fs NAMENODE -touchz /dir0/file1
-fs NAMENODE -touchz /dir0/file2
@@ -3226,6 +3238,7 @@
rm: removing files by globbing (relative path)
+ -fs NAMENODE -mkdir dir
-fs NAMENODE -touchz file0
-fs NAMENODE -touchz file1
-fs NAMENODE -touchz file2
@@ -3555,6 +3568,7 @@
rm: removing a file (absolute path)
+ -fs NAMENODE -mkdir /dir0
-fs NAMENODE -touchz /dir0/file0
-fs NAMENODE -rm -r /dir0/file0
@@ -3572,6 +3586,7 @@
rm: removing a file (relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -rm -r file0
@@ -3968,6 +3983,7 @@
put: putting file into a file (relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -put CLITEST_DATA/data15bytes data15bytes
-fs NAMENODE -du data15bytes
@@ -4066,6 +4082,7 @@
put: putting non existent file(absolute path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -put /user/wrongdata file
@@ -4083,6 +4100,7 @@
put: putting non existent file(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -put wrongdata file
@@ -4100,6 +4118,7 @@
put: putting file into an already existing destination(absolute path)
+ -fs NAMENODE -mkdir /user
-fs NAMENODE -touchz /user/file0
-fs NAMENODE -put CLITEST_DATA/data15bytes /user/file0
@@ -4117,6 +4136,7 @@
put: putting file into an already existing destination with -f option(absolute path)
+ -fs NAMENODE -mkdir /user
-fs NAMENODE -touchz /user/file0
-fs NAMENODE -put -f CLITEST_DATA/data120bytes /user/file0
-fs NAMENODE -cat /user/file0
@@ -4135,6 +4155,7 @@
put: putting file into an already existing destination(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -put CLITEST_DATA/data15bytes file0
@@ -4154,6 +4175,7 @@
-fs NAMENODE -put CLITEST_DATA/data15bytes /data15bytes
-fs NAMENODE -put CLITEST_DATA/data30bytes /data30bytes
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -put /data15bytes /data30bytes file0
@@ -4262,6 +4284,7 @@
put: putting local file into an already existing destination in hdfs:// path
+ -fs NAMENODE -mkdir hdfs:///user/
-fs NAMENODE -touchz hdfs:///user/file0
-fs NAMENODE -put CLITEST_DATA/data15bytes hdfs:///user/file0
@@ -4385,6 +4408,7 @@
put: putting local file into an already existing destination in Namenode's path
+ -fs NAMENODE -mkdir NAMENODE/user/
-fs NAMENODE -touchz NAMENODE/user/file0
-fs NAMENODE -put CLITEST_DATA/data15bytes NAMENODE/user/file0
@@ -4453,6 +4477,7 @@
copyFromLocal: copying file into a file (relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes data15bytes
-fs NAMENODE -du data15bytes
@@ -4551,6 +4576,7 @@
copyFromLocal: copying non existent file(absolute path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -copyFromLocal /user/wrongdata file
@@ -4568,6 +4594,7 @@
copyFromLocal: copying non existent file(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -copyFromLocal wrongdata file
@@ -4585,6 +4612,7 @@
copyFromLocal: copying file into an already existing destination(absolute path)
+ -fs NAMENODE -mkdir /user
-fs NAMENODE -touchz /user/file0
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes /user/file0
@@ -4602,6 +4630,7 @@
copyFromLocal: copying file into an already existing destination with -f option(absolute path)
+ -fs NAMENODE -mkdir /user
-fs NAMENODE -touchz /user/file0
-fs NAMENODE -copyFromLocal -f CLITEST_DATA/data120bytes /user/file0
-fs NAMENODE -cat /user/file0
@@ -4621,6 +4650,7 @@
copyFromLocal: copying file into an already existing destination(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes file0
@@ -4640,6 +4670,7 @@
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes /data15bytes
-fs NAMENODE -copyFromLocal CLITEST_DATA/data30bytes /data30bytes
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -copyFromLocal /data15bytes /data30bytes file0
@@ -4750,6 +4781,7 @@
copyFromLocal: Test for hdfs:// path - copying local file into an already existing destination
+ -fs NAMENODE -mkdir hdfs:///user/
-fs NAMENODE -touchz hdfs:///user/file0
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes hdfs:///user/file0
@@ -4876,6 +4908,7 @@
copyFromLocal: Test for Namenode's path - copying local file into an already existing destination
+ -fs NAMENODE -mkdir NAMENODE/user/
-fs NAMENODE -touchz NAMENODE/user/file0
-fs NAMENODE -copyFromLocal CLITEST_DATA/data15bytes NAMENODE/user/file0
@@ -5012,6 +5045,7 @@
cat: contents of file(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -put CLITEST_DATA/data15bytes data15bytes
-fs NAMENODE -cat data15bytes
@@ -5796,6 +5830,7 @@
setrep: existent file (absolute path)
+ -fs NAMENODE -mkdir /dir0
-fs NAMENODE -touchz /dir0/file0
-fs NAMENODE -setrep 2 /dir0/file0
@@ -5813,6 +5848,7 @@
setrep: existent file (relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -setrep 2 file0
@@ -5830,6 +5866,7 @@
setrep: existent directory (absolute path)
+ -fs NAMENODE -mkdir /dir0
-fs NAMENODE -touchz /dir0/file0
-fs NAMENODE -touchz /dir0/file1
-fs NAMENODE -setrep -R 2 /dir0
@@ -5852,6 +5889,7 @@
setrep: existent directory (relative path)
+ -fs NAMENODE -mkdir -p dir0
-fs NAMENODE -touchz dir0/file0
-fs NAMENODE -touchz dir0/file1
-fs NAMENODE -setrep -R 2 dir0
@@ -5906,6 +5944,7 @@
setrep: Test for hdfs:// path - existent file
+ -fs NAMENODE -mkdir hdfs:///dir0/
-fs NAMENODE -touchz hdfs:///dir0/file0
-fs NAMENODE -setrep 2 hdfs:///dir0/file0
@@ -5923,6 +5962,7 @@
setrep: Test for hdfs:// path - existent directory
+ -fs NAMENODE -mkdir hdfs:///dir0/
-fs NAMENODE -touchz hdfs:///dir0/file0
-fs NAMENODE -touchz hdfs:///dir0/file1
-fs NAMENODE -setrep -R 2 hdfs:///dir0
@@ -5945,6 +5985,7 @@
setrep: Test for hdfs:// path - non existent file
+ -fs NAMENODE -mkdir hdfs:///dir0/
-fs NAMENODE -setrep 2 hdfs:///dir0/file
@@ -5961,6 +6002,7 @@
setrep: Test for Namenode's path - existent file
+ -fs NAMENODE -mkdir NAMENODE/dir0/
-fs NAMENODE -touchz NAMENODE/dir0/file0
-fs NAMENODE -setrep 2 NAMENODE/dir0/file0
@@ -5978,6 +6020,7 @@
setrep: Test for Namenode's path - existent directory
+ -fs NAMENODE -mkdir -p NAMENODE/dir0
-fs NAMENODE -touchz NAMENODE/dir0/file0
-fs NAMENODE -touchz NAMENODE/dir0/file1
-fs NAMENODE -setrep -R 2 NAMENODE/dir0
@@ -6017,6 +6060,7 @@
touchz: touching file (absolute path)
+ -fs NAMENODE -mkdir /user
-fs NAMENODE -touchz /user/file0
-fs NAMENODE -du /user/file0
@@ -6030,10 +6074,26 @@
-
+
+
+ touchz: touching file in non-existent directory
+
+ -fs NAMENODE -touchz file0
+
+
+
+
+
+ RegexpComparator
+ touchz: `file0': No such file or directory
+
+
+
+
touchz: touching file(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -du file0
@@ -6051,6 +6111,7 @@
touchz: touching many files
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0 file1 file2
-fs NAMENODE -du file*
@@ -6070,6 +6131,7 @@
touchz: touching already existing file
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -put CLITEST_DATA/data15bytes data15bytes
-fs NAMENODE -touchz data15bytes
@@ -6087,6 +6149,7 @@
touchz: Test for hdfs:// path - touching file
+ -fs NAMENODE -mkdir -p hdfs:///user/
-fs NAMENODE -touchz hdfs:///user/file0
-fs NAMENODE -du hdfs:///user/file0
@@ -6340,6 +6403,7 @@
stat: statistics about file(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -put CLITEST_DATA/data60bytes data60bytes
-fs NAMENODE -stat "%n-%b" data60bytes
@@ -6430,6 +6494,7 @@
stat: statistics about files (relative path) using globbing
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -put CLITEST_DATA/data15bytes data15bytes
-fs NAMENODE -put CLITEST_DATA/data30bytes data30bytes
@@ -6690,6 +6755,7 @@
tail: contents of file(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -put CLITEST_DATA/data15bytes data15bytes
-fs NAMENODE -tail data15bytes
@@ -6968,6 +7034,7 @@
count: file using relative path
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -count file1
@@ -7145,6 +7212,7 @@
count: relative path to multiple files without globbing
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -touchz file2
-fs NAMENODE -touchz file3
@@ -7322,6 +7390,7 @@
count: file using relative path with -q option
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -count -q file1
@@ -7503,6 +7572,7 @@
count: relative path to multiple files without globbing with -q option
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -touchz file2
-fs NAMENODE -touchz file3
@@ -8462,6 +8532,7 @@
chmod: change permission(octal mode) of file in relative path
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file1
-fs NAMENODE -chmod 666 file1
-fs NAMENODE -ls file1
@@ -15988,6 +16059,7 @@
moveFromLocal: moving non existent file(relative path)
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz test
-fs NAMENODE -moveFromLocal wrongdata file
@@ -16007,6 +16079,7 @@
-fs NAMENODE -moveFromLocal CLITEST_DATA/data15bytes /data15bytes
-fs NAMENODE -moveFromLocal CLITEST_DATA/data30bytes /data30bytes
+ -fs NAMENODE -mkdir -p dir
-fs NAMENODE -touchz file0
-fs NAMENODE -moveFromLocal /data15bytes /data30bytes file0
From 7226dead1e66e501077fe1a0ec04369f03ecd2d2 Mon Sep 17 00:00:00 2001
From: Robert Joseph Evans
Date: Mon, 30 Jul 2012 20:58:44 +0000
Subject: [PATCH 36/39] HADOOP-8635. Cannot cancel paths registered
deleteOnExit (daryn via bobby)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1367296 13f79535-47bb-0310-9956-ffa450edef68
---
.../hadoop-common/CHANGES.txt | 2 +
.../java/org/apache/hadoop/fs/FileSystem.java | 10 +++
.../hadoop/fs/TestFileSystemCaching.java | 88 +++++++++++++++----
.../hadoop/fs/TestFilterFileSystem.java | 3 +
4 files changed, 87 insertions(+), 16 deletions(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index d1faef370a6..03e41de3da7 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -865,6 +865,8 @@ Release 0.23.3 - UNRELEASED
HADOOP-8550. hadoop fs -touchz automatically created parent directories
(John George via bobby)
+ HADOOP-8635. Cannot cancel paths registered deleteOnExit (daryn via bobby)
+
Release 0.23.2 - UNRELEASED
INCOMPATIBLE CHANGES
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 42b30c4999b..2fa4bd0c038 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
@@ -1214,6 +1214,16 @@ public abstract class FileSystem extends Configured implements Closeable {
}
return true;
}
+
+ /**
+ * Cancel the deletion of the path when the FileSystem is closed
+ * @param f the path to cancel deletion
+ */
+ public boolean cancelDeleteOnExit(Path f) {
+ synchronized (deleteOnExit) {
+ return deleteOnExit.remove(f);
+ }
+ }
/**
* Delete all files that were marked as delete-on-exit. This recursively
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
index e8debf8dfaf..7e5f99f5fb3 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFileSystemCaching.java
@@ -269,26 +269,82 @@ public class TestFileSystemCaching {
}
@Test
- public void testDeleteOnExitChecksExists() throws Exception {
+ public void testDelete() throws IOException {
+ FileSystem mockFs = mock(FileSystem.class);
+ FileSystem fs = new FilterFileSystem(mockFs);
+ Path path = new Path("/a");
+
+ fs.delete(path, false);
+ verify(mockFs).delete(eq(path), eq(false));
+ reset(mockFs);
+ fs.delete(path, true);
+ verify(mockFs).delete(eq(path), eq(true));
+ }
+
+ @Test
+ public void testDeleteOnExit() throws IOException {
FileSystem mockFs = mock(FileSystem.class);
FileSystem fs = new FilterFileSystem(mockFs);
- Path p = new Path("/a");
-
- // path has to exist for deleteOnExit to register it
- when(mockFs.getFileStatus(p)).thenReturn(new FileStatus());
- fs.deleteOnExit(p);
- verify(mockFs).getFileStatus(eq(p));
- fs.close();
- verify(mockFs).delete(eq(p), anyBoolean());
+ Path path = new Path("/a");
+
+ // delete on close if path does exist
+ when(mockFs.getFileStatus(eq(path))).thenReturn(new FileStatus());
+ assertTrue(fs.deleteOnExit(path));
+ verify(mockFs).getFileStatus(eq(path));
reset(mockFs);
-
- // make sure it doesn't try to delete a file that doesn't exist
- when(mockFs.getFileStatus(p)).thenReturn(new FileStatus());
- fs.deleteOnExit(p);
- verify(mockFs).getFileStatus(eq(p));
+ when(mockFs.getFileStatus(eq(path))).thenReturn(new FileStatus());
+ fs.close();
+ verify(mockFs).getFileStatus(eq(path));
+ verify(mockFs).delete(eq(path), eq(true));
+ }
+
+ @Test
+ public void testDeleteOnExitFNF() throws IOException {
+ FileSystem mockFs = mock(FileSystem.class);
+ FileSystem fs = new FilterFileSystem(mockFs);
+ Path path = new Path("/a");
+
+ // don't delete on close if path doesn't exist
+ assertFalse(fs.deleteOnExit(path));
+ verify(mockFs).getFileStatus(eq(path));
reset(mockFs);
fs.close();
- verify(mockFs).getFileStatus(eq(p));
+ verify(mockFs, never()).getFileStatus(eq(path));
verify(mockFs, never()).delete(any(Path.class), anyBoolean());
}
-}
\ No newline at end of file
+
+
+ @Test
+ public void testDeleteOnExitRemoved() throws IOException {
+ FileSystem mockFs = mock(FileSystem.class);
+ FileSystem fs = new FilterFileSystem(mockFs);
+ Path path = new Path("/a");
+
+ // don't delete on close if path existed, but later removed
+ when(mockFs.getFileStatus(eq(path))).thenReturn(new FileStatus());
+ assertTrue(fs.deleteOnExit(path));
+ verify(mockFs).getFileStatus(eq(path));
+ reset(mockFs);
+ fs.close();
+ verify(mockFs).getFileStatus(eq(path));
+ verify(mockFs, never()).delete(any(Path.class), anyBoolean());
+ }
+
+ @Test
+ public void testCancelDeleteOnExit() throws IOException {
+ FileSystem mockFs = mock(FileSystem.class);
+ FileSystem fs = new FilterFileSystem(mockFs);
+ Path path = new Path("/a");
+
+ // don't delete on close if path existed, but later cancelled
+ when(mockFs.getFileStatus(eq(path))).thenReturn(new FileStatus());
+ assertTrue(fs.deleteOnExit(path));
+ verify(mockFs).getFileStatus(eq(path));
+ assertTrue(fs.cancelDeleteOnExit(path));
+ assertFalse(fs.cancelDeleteOnExit(path)); // false because not registered
+ reset(mockFs);
+ fs.close();
+ verify(mockFs, never()).getFileStatus(any(Path.class));
+ verify(mockFs, never()).delete(any(Path.class), anyBoolean());
+ }
+}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFilterFileSystem.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFilterFileSystem.java
index a374c08a186..727986dcd12 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFilterFileSystem.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/TestFilterFileSystem.java
@@ -182,6 +182,9 @@ public class TestFilterFileSystem {
public boolean deleteOnExit(Path f) throws IOException {
return false;
}
+ public boolean cancelDeleteOnExit(Path f) throws IOException {
+ return false;
+ }
public String getScheme() {
return "dontcheck";
}
From 8a615793108ee560fb11efa5446ef0afe0b8413b Mon Sep 17 00:00:00 2001
From: Todd Lipcon
Date: Mon, 30 Jul 2012 22:38:43 +0000
Subject: [PATCH 37/39] Fix typo in CHANGES.txt: wrote HADOOP-8642 instead of
HADOOP-8624.
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1367347 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-common-project/hadoop-common/CHANGES.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt
index 03e41de3da7..cda7817987d 100644
--- a/hadoop-common-project/hadoop-common/CHANGES.txt
+++ b/hadoop-common-project/hadoop-common/CHANGES.txt
@@ -88,7 +88,7 @@ Trunk (unreleased changes)
HADOOP-8523. test-patch.sh doesn't validate patches before building
(Jack Dintruff via jeagles)
- HADOOP-8642. ProtobufRpcEngine should log all RPCs if TRACE logging is
+ HADOOP-8624. ProtobufRpcEngine should log all RPCs if TRACE logging is
enabled (todd)
BUG FIXES
From 56c7662b18b36941e6a6a2397624380a04f3bc48 Mon Sep 17 00:00:00 2001
From: Alejandro Abdelnur
Date: Mon, 30 Jul 2012 22:44:48 +0000
Subject: [PATCH 38/39] svn ignoring
hadoop-yarn-applications-unmanaged-am-launcher target directory (tucu)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1367349 13f79535-47bb-0310-9956-ffa450edef68
From e217a05255497fcf243ad7b379553db7fd0a2493 Mon Sep 17 00:00:00 2001
From: Alejandro Abdelnur
Date: Mon, 30 Jul 2012 22:56:15 +0000
Subject: [PATCH 39/39] MAPREDUCE-4342. Distributed Cache gives inconsistent
result if cache files get deleted from tasktracker. (mayank_bansal via tucu)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1367352 13f79535-47bb-0310-9956-ffa450edef68
---
hadoop-mapreduce-project/CHANGES.txt | 3 +
.../localizer/LocalResourcesTrackerImpl.java | 26 ++++++
.../TestLocalResourcesTrackerImpl.java | 83 +++++++++++++++++++
3 files changed, 112 insertions(+)
diff --git a/hadoop-mapreduce-project/CHANGES.txt b/hadoop-mapreduce-project/CHANGES.txt
index 620ccb84a33..8ca1299e6b5 100644
--- a/hadoop-mapreduce-project/CHANGES.txt
+++ b/hadoop-mapreduce-project/CHANGES.txt
@@ -156,6 +156,9 @@ Branch-2 ( Unreleased changes )
MAPREDUCE-4465. Update description of yarn.nodemanager.address property.
(bowang via tucu)
+ MAPREDUCE-4342. Distributed Cache gives inconsistent result if cache files
+ get deleted from tasktracker. (mayank_bansal via tucu)
+
Release 2.1.0-alpha - Unreleased
INCOMPATIBLE CHANGES
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/LocalResourcesTrackerImpl.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/LocalResourcesTrackerImpl.java
index 7127db97c08..01ec38397b6 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/LocalResourcesTrackerImpl.java
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/LocalResourcesTrackerImpl.java
@@ -17,6 +17,7 @@
*/
package org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer;
+import java.io.File;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -30,6 +31,7 @@ import org.apache.hadoop.yarn.api.records.LocalResourceVisibility;
import org.apache.hadoop.yarn.event.Dispatcher;
import org.apache.hadoop.yarn.server.nodemanager.DeletionService;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.event.ResourceEvent;
+import org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.event.ResourceEventType;
/**
* A collection of {@link LocalizedResource}s all of same
@@ -67,6 +69,12 @@ class LocalResourcesTrackerImpl implements LocalResourcesTracker {
switch (event.getType()) {
case REQUEST:
case LOCALIZED:
+ if (rsrc != null && (!isResourcePresent(rsrc))) {
+ LOG.info("Resource " + rsrc.getLocalPath()
+ + " is missing, localizing it again");
+ localrsrc.remove(req);
+ rsrc = null;
+ }
if (null == rsrc) {
rsrc = new LocalizedResource(req, dispatcher);
localrsrc.put(req, rsrc);
@@ -82,6 +90,24 @@ class LocalResourcesTrackerImpl implements LocalResourcesTracker {
rsrc.handle(event);
}
+ /**
+ * This module checks if the resource which was localized is already present
+ * or not
+ *
+ * @param rsrc
+ * @return true/false based on resource is present or not
+ */
+ public boolean isResourcePresent(LocalizedResource rsrc) {
+ boolean ret = true;
+ if (rsrc.getState() == ResourceState.LOCALIZED) {
+ File file = new File(rsrc.getLocalPath().toUri().getRawPath().toString());
+ if (!file.exists()) {
+ ret = false;
+ }
+ }
+ return ret;
+ }
+
@Override
public boolean contains(LocalResourceRequest resource) {
return localrsrc.containsKey(resource);
diff --git a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/TestLocalResourcesTrackerImpl.java b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/TestLocalResourcesTrackerImpl.java
index 7f0e3536975..3ee623cecaf 100644
--- a/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/TestLocalResourcesTrackerImpl.java
+++ b/hadoop-mapreduce-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/TestLocalResourcesTrackerImpl.java
@@ -5,6 +5,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import java.io.File;
+import java.io.IOException;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -30,6 +32,7 @@ import org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.even
import org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.event.ResourceRequestEvent;
import org.apache.hadoop.yarn.util.BuilderUtils;
import org.junit.Test;
+import org.mortbay.log.Log;
public class TestLocalResourcesTrackerImpl {
@@ -131,6 +134,86 @@ public class TestLocalResourcesTrackerImpl {
}
}
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testConsistency() {
+ String user = "testuser";
+ DrainDispatcher dispatcher = null;
+ try {
+ dispatcher = createDispatcher(new Configuration());
+ EventHandler localizerEventHandler = mock(EventHandler.class);
+ EventHandler containerEventHandler = mock(EventHandler.class);
+ dispatcher.register(LocalizerEventType.class, localizerEventHandler);
+ dispatcher.register(ContainerEventType.class, containerEventHandler);
+
+ ContainerId cId1 = BuilderUtils.newContainerId(1, 1, 1, 1);
+ LocalizerContext lc1 = new LocalizerContext(user, cId1, null);
+ LocalResourceRequest req1 = createLocalResourceRequest(user, 1, 1,
+ LocalResourceVisibility.PUBLIC);
+ LocalizedResource lr1 = createLocalizedResource(req1, dispatcher);
+ ConcurrentMap localrsrc = new ConcurrentHashMap();
+ localrsrc.put(req1, lr1);
+ LocalResourcesTracker tracker = new LocalResourcesTrackerImpl(user,
+ dispatcher, localrsrc);
+
+ ResourceEvent req11Event = new ResourceRequestEvent(req1,
+ LocalResourceVisibility.PUBLIC, lc1);
+
+ ResourceEvent rel11Event = new ResourceReleaseEvent(req1, cId1);
+
+ // Localize R1 for C1
+ tracker.handle(req11Event);
+
+ dispatcher.await();
+
+ // Verify refCount for R1 is 1
+ Assert.assertEquals(1, lr1.getRefCount());
+
+ dispatcher.await();
+ verifyTrackedResourceCount(tracker, 1);
+
+ // Localize resource1
+ ResourceLocalizedEvent rle = new ResourceLocalizedEvent(req1, new Path(
+ "file:///tmp/r1"), 1);
+ lr1.handle(rle);
+ Assert.assertTrue(lr1.getState().equals(ResourceState.LOCALIZED));
+ Assert.assertTrue(createdummylocalizefile(new Path("file:///tmp/r1")));
+ LocalizedResource rsrcbefore = tracker.iterator().next();
+ File resFile = new File(lr1.getLocalPath().toUri().getRawPath()
+ .toString());
+ Assert.assertTrue(resFile.exists());
+ Assert.assertTrue(resFile.delete());
+
+ // Localize R1 for C1
+ tracker.handle(req11Event);
+
+ dispatcher.await();
+ lr1.handle(rle);
+ Assert.assertTrue(lr1.getState().equals(ResourceState.LOCALIZED));
+ LocalizedResource rsrcafter = tracker.iterator().next();
+ if (rsrcbefore == rsrcafter) {
+ Assert.fail("Localized resource should not be equal");
+ }
+ // Release resource1
+ tracker.handle(rel11Event);
+ } finally {
+ if (dispatcher != null) {
+ dispatcher.stop();
+ }
+ }
+ }
+
+ private boolean createdummylocalizefile(Path path) {
+ boolean ret = false;
+ File file = new File(path.toUri().getRawPath().toString());
+ try {
+ ret = file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return ret;
+ }
+
private void verifyTrackedResourceCount(LocalResourcesTracker tracker,
int expected) {
int count = 0;