Repository: tez
Updated Branches:
  refs/heads/branch-0.5 5d0211a38 -> abca9f0bf


TEZ-1529. ATS and TezClient integration in secure kerberos enabled cluster. 
(pramachandran)

(cherry picked from commit 8710df0d1264a453218220ed48e5d2b5d2923da1)

Conflicts:
        CHANGES.txt
        
tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientTimelineImpl.java


Project: http://git-wip-us.apache.org/repos/asf/tez/repo
Commit: http://git-wip-us.apache.org/repos/asf/tez/commit/abca9f0b
Tree: http://git-wip-us.apache.org/repos/asf/tez/tree/abca9f0b
Diff: http://git-wip-us.apache.org/repos/asf/tez/diff/abca9f0b

Branch: refs/heads/branch-0.5
Commit: abca9f0bffd645eb1bab75e226f49f85d50f0a66
Parents: 5d0211a
Author: Prakash Ramachandran <[email protected]>
Authored: Wed May 27 17:59:17 2015 +0530
Committer: Prakash Ramachandran <[email protected]>
Committed: Sun Jun 21 01:40:07 2015 +0530

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../org/apache/tez/common/ReflectionUtils.java  |  19 +
 .../tez/dag/api/client/DAGClientImpl.java       |  12 +-
 .../dag/api/client/DAGClientTimelineImpl.java   |  56 ++-
 .../dag/api/client/TimelineReaderFactory.java   | 387 +++++++++++++++++++
 .../tez/dag/api/client/TestATSHttpClient.java   |   6 +-
 .../api/client/TestTimelineReaderFactory.java   |  91 +++++
 7 files changed, 530 insertions(+), 42 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tez/blob/abca9f0b/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 1ba12d0..cf18ba1 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,7 @@ Apache Tez Change Log
 Release 0.5.4: Unreleased
 
 ALL CHANGES:
+  TEZ-1529. ATS and TezClient integration in secure kerberos enabled cluster.
   TEZ-2557. Port TEZ-1910 to branch-0.5.
   TEZ-2566. Allow TaskAttemptFinishedEvent without TaskAttemptStartedEvent 
when it is KILLED/FAILED
   TEZ-2475. Fix a potential hang in Tez local mode caused by incorrectly 
handled interrupts.

http://git-wip-us.apache.org/repos/asf/tez/blob/abca9f0b/tez-api/src/main/java/org/apache/tez/common/ReflectionUtils.java
----------------------------------------------------------------------
diff --git a/tez-api/src/main/java/org/apache/tez/common/ReflectionUtils.java 
b/tez-api/src/main/java/org/apache/tez/common/ReflectionUtils.java
index 0fc529b..f1eb0ae 100644
--- a/tez-api/src/main/java/org/apache/tez/common/ReflectionUtils.java
+++ b/tez-api/src/main/java/org/apache/tez/common/ReflectionUtils.java
@@ -100,6 +100,25 @@ public class ReflectionUtils {
   }
 
   @Private
+  @SuppressWarnings("unchecked")
+  public static <T> T invokeMethod(Object target, Method method, Object... 
args) {
+    try {
+      return (T) method.invoke(target, args);
+    } catch (Exception e) {
+      throw new TezUncheckedException(e);
+    }
+  }
+
+  @Private
+  public static Method getMethod(Class<?> targetClazz, String methodName, 
Class<?>... parameterTypes) {
+    try {
+      return targetClazz.getMethod(methodName, parameterTypes);
+    } catch (NoSuchMethodException e) {
+      throw new TezUncheckedException(e);
+    }
+  }
+
+  @Private
   public static synchronized void addResourcesToClasspath(List<URL> urls) {
     ClassLoader classLoader = new URLClassLoader(urls.toArray(new 
URL[urls.size()]), Thread
         .currentThread().getContextClassLoader());

http://git-wip-us.apache.org/repos/asf/tez/blob/abca9f0b/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientImpl.java
----------------------------------------------------------------------
diff --git 
a/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientImpl.java 
b/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientImpl.java
index 87e64cd..4c8bd2a 100644
--- a/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientImpl.java
+++ b/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientImpl.java
@@ -31,7 +31,6 @@ import com.google.common.annotations.VisibleForTesting;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.classification.InterfaceAudience.Private;
-import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.yarn.api.records.ApplicationId;
 import org.apache.hadoop.yarn.api.records.ApplicationReport;
 import org.apache.hadoop.yarn.api.records.YarnApplicationState;
@@ -84,12 +83,8 @@ public class DAGClientImpl extends DAGClient {
       this.frameworkClient.start();
     }
     isATSEnabled = 
conf.get(TezConfiguration.TEZ_HISTORY_LOGGING_SERVICE_CLASS, "")
-            
.equals("org.apache.tez.dag.history.logging.ats.ATSHistoryLoggingService");
-
-    if (UserGroupInformation.isSecurityEnabled()){
-      //TODO: enable ATS integration in kerberos secured cluster - see TEZ-1529
-      isATSEnabled = false;
-    }
+            
.equals("org.apache.tez.dag.history.logging.ats.ATSHistoryLoggingService") &&
+            DAGClientTimelineImpl.isSupported();
 
     realClient = new DAGClientRPCImpl(appId, dagId, conf, 
this.frameworkClient);
   }
@@ -443,7 +438,8 @@ public class DAGClientImpl extends DAGClient {
 
   private void switchToTimelineClient() throws IOException, TezException {
     realClient.close();
-    realClient = new DAGClientTimelineImpl(appId, dagId, conf, 
frameworkClient);
+    realClient = new DAGClientTimelineImpl(appId, dagId, conf, frameworkClient,
+        (int) (2 * PRINT_STATUS_INTERVAL_MILLIS));
     if (LOG.isDebugEnabled()) {
       LOG.debug("dag completed switching to DAGClientTimelineImpl");
     }

http://git-wip-us.apache.org/repos/asf/tez/blob/abca9f0b/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientTimelineImpl.java
----------------------------------------------------------------------
diff --git 
a/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientTimelineImpl.java
 
b/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientTimelineImpl.java
index 6cbab80..8fef2d5 100644
--- 
a/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientTimelineImpl.java
+++ 
b/tez-api/src/main/java/org/apache/tez/dag/api/client/DAGClientTimelineImpl.java
@@ -23,9 +23,6 @@ import javax.ws.rs.core.MediaType;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -40,16 +37,10 @@ import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.UniformInterfaceException;
 import com.sun.jersey.api.client.WebResource;
-import com.sun.jersey.api.client.config.ClientConfig;
-import com.sun.jersey.api.client.config.DefaultClientConfig;
-import com.sun.jersey.client.urlconnection.HttpURLConnectionFactory;
-import com.sun.jersey.client.urlconnection.URLConnectionClientHandler;
-import com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.classification.InterfaceAudience.Private;
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.yarn.api.records.ApplicationId;
 import org.apache.hadoop.yarn.api.records.ApplicationReport;
 import org.apache.hadoop.yarn.exceptions.YarnException;
@@ -78,10 +69,10 @@ public class DAGClientTimelineImpl extends DAGClient {
   private static final String FILTER_BY_FIELDS = "primaryfilters,otherinfo";
   private static final String HTTPS_SCHEME = "https://";;
   private static final String HTTP_SCHEME = "http://";;
-  private static Client httpClient = null;
+  private Client httpClient = null;
+  private final TimelineReaderFactory.TimelineReaderStrategy 
timelineReaderStrategy;
   private final ApplicationId appId;
   private final String dagId;
-  private final TezConfiguration conf;
   private final FrameworkClient frameworkClient;
 
   private Map<String, VertexTaskStats> vertexTaskStatsCache = null;
@@ -90,16 +81,21 @@ public class DAGClientTimelineImpl extends DAGClient {
   protected String baseUri;
 
   public DAGClientTimelineImpl(ApplicationId appId, String dagId, 
TezConfiguration conf,
-                               FrameworkClient frameworkClient)
+                               FrameworkClient frameworkClient, int 
connTimeout)
       throws TezException {
+
+    if (!TimelineReaderFactory.isTimelineClientSupported()) {
+      throw new TezException("Reading from secure timeline is supported only 
for hadoop 2.6 and above.");
+    }
+
     this.appId = appId;
     this.dagId = dagId;
-    this.conf = conf;
     this.frameworkClient = frameworkClient;
 
     String scheme;
     String webAppAddress;
-    if (webappHttpsOnly(conf)) {
+    boolean useHttps = webappHttpsOnly(conf);
+    if (useHttps) {
       scheme = HTTPS_SCHEME;
       webAppAddress = 
conf.get(ATSConstants.TIMELINE_SERVICE_WEBAPP_HTTPS_ADDRESS_CONF_NAME);
     } else {
@@ -111,8 +107,14 @@ public class DAGClientTimelineImpl extends DAGClient {
     }
 
     baseUri = Joiner.on("").join(scheme, webAppAddress, 
ATSConstants.RESOURCE_URI_BASE);
+
+    timelineReaderStrategy =
+        TimelineReaderFactory.getTimelineReaderStrategy(conf, useHttps, 
connTimeout);
   }
 
+  public static boolean isSupported() {
+    return TimelineReaderFactory.isTimelineClientSupported();
+  }
 
   @Override
   public String getExecutionContext() {
@@ -407,13 +409,15 @@ public class DAGClientTimelineImpl extends DAGClient {
   @VisibleForTesting
   protected JSONObject getJsonRootEntity(String url) throws TezException {
     try {
-      WebResource wr = getHttpClient().resource(url);
+      WebResource wr = getCachedHttpClient().resource(url);
       ClientResponse response = wr.accept(MediaType.APPLICATION_JSON_TYPE)
           .type(MediaType.APPLICATION_JSON_TYPE)
           .get(ClientResponse.class);
 
-      if (response.getClientResponseStatus() != ClientResponse.Status.OK) {
-        throw new TezException("Failed to get response from YARN Timeline: 
url: " + url);
+      final ClientResponse.Status clientResponseStatus = 
response.getClientResponseStatus();
+      if (clientResponseStatus != ClientResponse.Status.OK) {
+        throw new TezException("Failed to get response from YARN Timeline:" +
+            " errorCode:" + clientResponseStatus + ", url:" + url);
       }
 
       return response.getEntity(JSONObject.class);
@@ -423,6 +427,8 @@ public class DAGClientTimelineImpl extends DAGClient {
       throw new TezException("Error accessing content from YARN Timeline - 
unexpected response", e);
     } catch (IllegalArgumentException e) {
       throw new TezException("Error accessing content from YARN Timeline - 
invalid url", e);
+    } catch (IOException e) {
+      throw new TezException("Error failed to get http client", e);
     }
   }
 
@@ -460,11 +466,9 @@ public class DAGClientTimelineImpl extends DAGClient {
     }
   }
 
-  protected Client getHttpClient() {
+  protected Client getCachedHttpClient() throws IOException {
     if (httpClient == null) {
-      ClientConfig config = new 
DefaultClientConfig(JSONRootElementProvider.App.class);
-      HttpURLConnectionFactory urlFactory = new 
PseudoAuthenticatedURLConnectionFactory();
-      httpClient = new Client(new URLConnectionClientHandler(urlFactory), 
config);
+      httpClient = timelineReaderStrategy.getHttpClient();
     }
     return httpClient;
   }
@@ -495,14 +499,4 @@ public class DAGClientTimelineImpl extends DAGClient {
         put("TERMINATING", VertexStatusStateProto.VERTEX_TERMINATING);
       }});
 
-
-  class PseudoAuthenticatedURLConnectionFactory implements 
HttpURLConnectionFactory {
-    @Override
-    public HttpURLConnection getHttpURLConnection(URL url) throws IOException {
-      String tokenString = (url.getQuery() == null ? "?" : "&") + "user.name=" 
+
-          
URLEncoder.encode(UserGroupInformation.getCurrentUser().getShortUserName(), 
"UTF8");
-      return (HttpURLConnection) (new URL(url.toString() + 
tokenString)).openConnection();
-    }
-  }
-
 }

http://git-wip-us.apache.org/repos/asf/tez/blob/abca9f0b/tez-api/src/main/java/org/apache/tez/dag/api/client/TimelineReaderFactory.java
----------------------------------------------------------------------
diff --git 
a/tez-api/src/main/java/org/apache/tez/dag/api/client/TimelineReaderFactory.java
 
b/tez-api/src/main/java/org/apache/tez/dag/api/client/TimelineReaderFactory.java
new file mode 100644
index 0000000..f544198
--- /dev/null
+++ 
b/tez-api/src/main/java/org/apache/tez/dag/api/client/TimelineReaderFactory.java
@@ -0,0 +1,387 @@
+/**
+ * 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.tez.dag.api.client;
+
+import static org.apache.hadoop.security.ssl.SSLFactory.Mode.CLIENT;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.security.GeneralSecurityException;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.config.ClientConfig;
+import com.sun.jersey.api.client.config.DefaultClientConfig;
+import com.sun.jersey.client.urlconnection.HttpURLConnectionFactory;
+import com.sun.jersey.client.urlconnection.URLConnectionClientHandler;
+import com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider;
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.conf.Configuration;
+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.authentication.client.ConnectionConfigurator;
+import org.apache.hadoop.security.ssl.SSLFactory;
+import org.apache.tez.common.ReflectionUtils;
+import org.apache.tez.dag.api.TezException;
+import org.apache.tez.dag.api.TezUncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/*
+ *  TimelineReaderFactory getTimelineReaderStrategy returns a Strategy class, 
which is used to
+ *  create a httpclient, configured for the appropriate runtime.
+ *
+ *  on hadoop 2.6+ the factory returns 
TimelineReaderTokenAuthenticatedStrategy, which supports
+ *  kerberos based auth (secure cluster) or psuedo auth (un-secure cluster).
+ *
+ *  on hadoop 2.4 where the token delegation auth is not supported, 
TimelineReaderPseudoAuthenticatedStrategy
+ *  is used which supports only unsecure timeline.
+ *
+ */
[email protected]
+public class TimelineReaderFactory {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(TimelineReaderFactory.class);
+
+  private static final String 
KERBEROS_DELEGATION_TOKEN_AUTHENTICATOR_CLAZZ_NAME =
+      
"org.apache.hadoop.security.token.delegation.web.KerberosDelegationTokenAuthenticator";
+  private static final String PSEUDO_DELEGATION_TOKEN_AUTHENTICATOR_CLAZZ_NAME 
=
+      
"org.apache.hadoop.security.token.delegation.web.PseudoDelegationTokenAuthenticator";
+  private static final String DELEGATION_TOKEN_AUTHENTICATED_URL_CLAZZ_NAME =
+      
"org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticatedURL";
+  private static final String DELEGATION_TOKEN_AUTHENTICATOR_CLAZZ_NAME =
+      
"org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticator";
+  private static final String 
DELEGATION_TOKEN_AUTHENTICATED_URL_TOKEN_CLASS_NAME =
+      
"org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticatedURL$Token";
+
+  private static Class<?> delegationTokenAuthenticatorClazz = null;
+  private static Method delegationTokenAuthenticateURLOpenConnectionMethod = 
null;
+
+  public static TimelineReaderStrategy getTimelineReaderStrategy(Configuration 
conf,
+                                                                 boolean 
useHttps,
+                                                                 int 
connTimeout) throws TezException {
+
+    TimelineReaderStrategy timelineReaderStrategy;
+
+    if (!isTimelineClientSupported()) {
+      throw new TezException("Reading from timeline is not supported." +
+          " token delegation support: " + tokenDelegationSupported() +
+          ", is secure timeline: " + UserGroupInformation.isSecurityEnabled());
+    }
+
+    timelineReaderStrategy = 
getTimelineReaderStrategy(tokenDelegationSupported(), conf, useHttps,
+        connTimeout);
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Using " + timelineReaderStrategy.getClass().getName() + " to 
read timeline data");
+    }
+
+    return timelineReaderStrategy;
+  }
+
+  private static TimelineReaderStrategy getTimelineReaderStrategy(boolean 
isTokenDelegationSupported,
+                                                                    
Configuration conf,
+                                                                    boolean 
useHttps,
+                                                                    int 
connTimeout) {
+    TimelineReaderStrategy timelineReaderStrategy;
+
+    if (isTokenDelegationSupported) {
+      timelineReaderStrategy =
+          new TimelineReaderTokenAuthenticatedStrategy(conf, useHttps, 
connTimeout);
+    } else {
+      timelineReaderStrategy =
+          new TimelineReaderPseudoAuthenticatedStrategy(conf, useHttps, 
connTimeout);
+    }
+
+    return timelineReaderStrategy;
+  }
+
+  /**
+   * Check if timeline client can be supported.
+   *
+   * @return boolean value indicating if timeline client to read data is 
supported.
+   */
+  public static boolean isTimelineClientSupported() {
+    // support to read data from timeline is based on the version of hadoop.
+    // reads are supported for non-secure cluster from hadoop 2.4 and up.
+    // reads are supported for secure cluster only from hadoop 2.6. check the 
presence of the classes
+    // required upfront if security is enabled.
+    return !UserGroupInformation.isSecurityEnabled() || 
tokenDelegationSupported();
+  }
+
+  public interface TimelineReaderStrategy {
+    Client getHttpClient() throws IOException;
+  }
+
+  /*
+   * auth strategy for secured and unsecured environment with delegation token 
(hadoop 2.6 and above)
+   */
+  private static class TimelineReaderTokenAuthenticatedStrategy implements 
TimelineReaderStrategy {
+    private final Configuration conf;
+    private final boolean useHttps;
+    private final int connTimeout;
+
+    public TimelineReaderTokenAuthenticatedStrategy(final Configuration conf,
+                                                    final boolean useHttps,
+                                                    final int connTimeout) {
+
+      this.conf = conf;
+      this.useHttps = useHttps;
+      this.connTimeout = connTimeout;
+    }
+
+    @Override
+    public Client getHttpClient() throws IOException {
+      Authenticator authenticator;
+      UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
+      UserGroupInformation realUgi = ugi.getRealUser();
+      UserGroupInformation authUgi;
+      String doAsUser;
+      ClientConfig clientConfig = new 
DefaultClientConfig(JSONRootElementProvider.App.class);
+      ConnectionConfigurator connectionConfigurator = 
getNewConnectionConf(conf, useHttps,
+          connTimeout);
+
+      try {
+        authenticator = getTokenAuthenticator();
+        authenticator.setConnectionConfigurator(connectionConfigurator);
+      } catch (TezUncheckedException e) {
+        throw new IOException("Failed to get authenticator", e);
+      }
+
+      if (realUgi != null) {
+        authUgi = realUgi;
+        doAsUser = ugi.getShortUserName();
+      } else {
+        authUgi = ugi;
+        doAsUser = null;
+      }
+
+      HttpURLConnectionFactory connectionFactory =
+          new TokenAuthenticatedURLConnectionFactory(connectionConfigurator, 
authenticator,
+              authUgi, doAsUser);
+      return new Client(new URLConnectionClientHandler(connectionFactory), 
clientConfig);
+    }
+
+    private static Authenticator getTokenAuthenticator() {
+      String authenticatorClazzName;
+
+      if (UserGroupInformation.isSecurityEnabled()) {
+        authenticatorClazzName = 
KERBEROS_DELEGATION_TOKEN_AUTHENTICATOR_CLAZZ_NAME;
+      } else {
+        authenticatorClazzName = 
PSEUDO_DELEGATION_TOKEN_AUTHENTICATOR_CLAZZ_NAME;
+      }
+
+      return ReflectionUtils.createClazzInstance(authenticatorClazzName);
+    }
+
+    private static class TokenAuthenticatedURLConnectionFactory implements 
HttpURLConnectionFactory {
+
+      private final Authenticator authenticator;
+      private final ConnectionConfigurator connConfigurator;
+      private final UserGroupInformation authUgi;
+      private final String doAsUser;
+      private final AuthenticatedURL.Token token;
+
+      public TokenAuthenticatedURLConnectionFactory(ConnectionConfigurator 
connConfigurator,
+                                                    Authenticator 
authenticator,
+                                                    UserGroupInformation 
authUgi,
+                                                    String doAsUser) {
+        this.connConfigurator = connConfigurator;
+        this.authenticator = authenticator;
+        this.authUgi = authUgi;
+        this.doAsUser = doAsUser;
+        this.token = ReflectionUtils.createClazzInstance(
+            DELEGATION_TOKEN_AUTHENTICATED_URL_TOKEN_CLASS_NAME, null, null);
+      }
+
+      @Override
+      public HttpURLConnection getHttpURLConnection(URL url) throws 
IOException {
+        try {
+          AuthenticatedURL authenticatedURL= 
ReflectionUtils.createClazzInstance(
+              DELEGATION_TOKEN_AUTHENTICATED_URL_CLAZZ_NAME, new Class[] {
+              delegationTokenAuthenticatorClazz,
+              ConnectionConfigurator.class
+          }, new Object[] {
+              authenticator,
+              connConfigurator
+          });
+          return ReflectionUtils.invokeMethod(authenticatedURL,
+              delegationTokenAuthenticateURLOpenConnectionMethod, url, token, 
doAsUser);
+        } catch (Exception e) {
+          throw new IOException(e);
+        }
+      }
+    }
+  }
+
+  /*
+   * Pseudo auth strategy for env where delegation token is not supported 
(hadoop 2.4)
+   */
+  @VisibleForTesting
+  protected static class TimelineReaderPseudoAuthenticatedStrategy implements 
TimelineReaderStrategy {
+
+    private final ConnectionConfigurator connectionConf;
+
+    public TimelineReaderPseudoAuthenticatedStrategy(final Configuration conf,
+                                                     final boolean useHttps,
+                                                     final int connTimeout) {
+      connectionConf = getNewConnectionConf(conf, useHttps, connTimeout);
+    }
+
+    @Override
+    public Client getHttpClient() {
+      ClientConfig config = new 
DefaultClientConfig(JSONRootElementProvider.App.class);
+      HttpURLConnectionFactory urlFactory = new 
PseudoAuthenticatedURLConnectionFactory(connectionConf);
+      Client httpClient = new Client(new 
URLConnectionClientHandler(urlFactory), config);
+      return httpClient;
+    }
+
+    @VisibleForTesting
+    protected static class PseudoAuthenticatedURLConnectionFactory implements 
HttpURLConnectionFactory {
+      private final ConnectionConfigurator connectionConf;
+
+      public PseudoAuthenticatedURLConnectionFactory(ConnectionConfigurator 
connectionConf) {
+        this.connectionConf = connectionConf;
+      }
+
+      @Override
+      public HttpURLConnection getHttpURLConnection(URL url) throws 
IOException {
+        String tokenString = (url.getQuery() == null ? "?" : "&") + 
"user.name=" +
+            
URLEncoder.encode(UserGroupInformation.getCurrentUser().getShortUserName(), 
"UTF8");
+
+        HttpURLConnection httpURLConnection =
+            (HttpURLConnection) (new URL(url.toString() + 
tokenString)).openConnection();
+        this.connectionConf.configure(httpURLConnection);
+
+        return httpURLConnection;
+      }
+    }
+  }
+
+  private static ConnectionConfigurator getNewConnectionConf(final 
Configuration conf,
+                                                             final boolean 
useHttps,
+                                                             final int 
connTimeout) {
+    ConnectionConfigurator connectionConf = null;
+    if (useHttps) {
+      try {
+        connectionConf = getNewSSLConnectionConf(conf, connTimeout);
+      } catch (IOException e) {
+        if (LOG.isDebugEnabled()) {
+          LOG.debug("Cannot load customized ssl related configuration."
+              + " Falling back to system-generic settings.", e);
+        }
+      }
+    }
+
+    if (connectionConf == null) {
+      connectionConf = new ConnectionConfigurator() {
+        @Override
+        public HttpURLConnection configure(HttpURLConnection 
httpURLConnection) throws IOException {
+          setTimeouts(httpURLConnection, connTimeout);
+          return httpURLConnection;
+        }
+      };
+    }
+
+    return connectionConf;
+  }
+
+  private static ConnectionConfigurator getNewSSLConnectionConf(final 
Configuration conf,
+                                                                final int 
connTimeout)
+      throws IOException {
+    final SSLFactory sslFactory;
+    final SSLSocketFactory sslSocketFactory;
+    final HostnameVerifier hostnameVerifier;
+
+    sslFactory = new SSLFactory(CLIENT, conf);
+    try {
+      sslFactory.init();
+      sslSocketFactory = sslFactory.createSSLSocketFactory();
+    } catch (GeneralSecurityException e) {
+      sslFactory.destroy();
+      throw new IOException("Failed to initialize ssl factory");
+    }
+    hostnameVerifier = sslFactory.getHostnameVerifier();
+
+    return new ConnectionConfigurator() {
+      @Override
+      public HttpURLConnection configure(HttpURLConnection httpURLConnection) 
throws IOException {
+        if (!(httpURLConnection instanceof HttpsURLConnection)) {
+          throw new IOException("Expected https connection");
+        }
+        HttpsURLConnection httpsURLConnection = (HttpsURLConnection) 
httpURLConnection;
+        httpsURLConnection.setSSLSocketFactory(sslSocketFactory);
+        httpsURLConnection.setHostnameVerifier(hostnameVerifier);
+        setTimeouts(httpsURLConnection, connTimeout);
+
+        return httpsURLConnection;
+      }
+    };
+  }
+
+  private static void setTimeouts(HttpURLConnection httpURLConnection, int 
connTimeout) {
+    httpURLConnection.setConnectTimeout(connTimeout);
+    httpURLConnection.setReadTimeout(connTimeout);
+  }
+
+  private static boolean isTokenDelegationSupportChecksDone = false;
+  private static boolean isTokenDelegationClassesPresent = false;
+
+  // Check if all the classes required for doing token authentication are 
present. These classes
+  // are present only from hadoop 2.6 onwards.
+  private static synchronized boolean tokenDelegationSupported() {
+
+    if (!isTokenDelegationSupportChecksDone) {
+
+      isTokenDelegationSupportChecksDone = true;
+
+      try {
+        
ReflectionUtils.getClazz(KERBEROS_DELEGATION_TOKEN_AUTHENTICATOR_CLAZZ_NAME);
+        
ReflectionUtils.getClazz(PSEUDO_DELEGATION_TOKEN_AUTHENTICATOR_CLAZZ_NAME);
+
+        delegationTokenAuthenticatorClazz =
+            
ReflectionUtils.getClazz(DELEGATION_TOKEN_AUTHENTICATOR_CLAZZ_NAME);
+
+        Class<?> delegationTokenAuthenticatedURLClazz =
+            
ReflectionUtils.getClazz(DELEGATION_TOKEN_AUTHENTICATED_URL_CLAZZ_NAME);
+
+        Class<?> delegationTokenAuthenticatedURLTokenClazz =
+            
ReflectionUtils.getClazz(DELEGATION_TOKEN_AUTHENTICATED_URL_TOKEN_CLASS_NAME);
+
+        delegationTokenAuthenticateURLOpenConnectionMethod =
+            ReflectionUtils.getMethod(delegationTokenAuthenticatedURLClazz, 
"openConnection",
+                URL.class, delegationTokenAuthenticatedURLTokenClazz, 
String.class);
+
+        isTokenDelegationClassesPresent = true;
+
+      } catch (TezUncheckedException e) {
+        LOG.info("Could not find class required for token delegation, will 
fallback to pseudo auth");
+      }
+    }
+
+    return isTokenDelegationClassesPresent;
+  }
+}

http://git-wip-us.apache.org/repos/asf/tez/blob/abca9f0b/tez-api/src/test/java/org/apache/tez/dag/api/client/TestATSHttpClient.java
----------------------------------------------------------------------
diff --git 
a/tez-api/src/test/java/org/apache/tez/dag/api/client/TestATSHttpClient.java 
b/tez-api/src/test/java/org/apache/tez/dag/api/client/TestATSHttpClient.java
index 428f103..bd494eb 100644
--- a/tez-api/src/test/java/org/apache/tez/dag/api/client/TestATSHttpClient.java
+++ b/tez-api/src/test/java/org/apache/tez/dag/api/client/TestATSHttpClient.java
@@ -56,7 +56,7 @@ public class TestATSHttpClient {
   public void testGetDagStatusThrowsExceptionOnEmptyJson() throws TezException 
{
     ApplicationId mockAppId = mock(ApplicationId.class);
     DAGClientTimelineImpl httpClient = new DAGClientTimelineImpl(mockAppId, 
"EXAMPLE_DAG_ID",
-        new TezConfiguration(), null);
+        new TezConfiguration(), null, 0);
     DAGClientTimelineImpl spyClient = spy(httpClient);
     spyClient.baseUri = "http://yarn.ats.webapp/ws/v1/timeline";;
     final String expectedDagUrl = 
"http://yarn.ats.webapp/ws/v1/timeline/TEZ_DAG_ID/EXAMPLE_DAG_ID"; +
@@ -80,7 +80,7 @@ public class TestATSHttpClient {
   public void testGetDagStatusSimple() throws TezException, JSONException, 
IOException {
     DAGClientTimelineImpl
         httpClient = new 
DAGClientTimelineImpl(mock(ApplicationId.class),"EXAMPLE_DAG_ID",
-        new TezConfiguration(), null);
+        new TezConfiguration(), null, 0);
     DAGClientTimelineImpl spyClient = spy(httpClient);
     spyClient.baseUri = "http://yarn.ats.webapp/ws/v1/timeline";;
     final String expectedDagUrl = 
"http://yarn.ats.webapp/ws/v1/timeline/TEZ_DAG_ID/EXAMPLE_DAG_ID"; +
@@ -140,7 +140,7 @@ public class TestATSHttpClient {
   public void testGetVertexStatusSimple() throws JSONException, TezException, 
IOException {
     DAGClientTimelineImpl
         httpClient = new DAGClientTimelineImpl(mock(ApplicationId.class), 
"EXAMPLE_DAG_ID",
-        new TezConfiguration(), null);
+        new TezConfiguration(), null, 0);
     DAGClientTimelineImpl spyClient = spy(httpClient);
     spyClient.baseUri = "http://yarn.ats.webapp/ws/v1/timeline";;
     final String expectedVertexUrl = 
"http://yarn.ats.webapp/ws/v1/timeline/TEZ_VERTEX_ID"; +

http://git-wip-us.apache.org/repos/asf/tez/blob/abca9f0b/tez-api/src/test/java/org/apache/tez/dag/api/client/TestTimelineReaderFactory.java
----------------------------------------------------------------------
diff --git 
a/tez-api/src/test/java/org/apache/tez/dag/api/client/TestTimelineReaderFactory.java
 
b/tez-api/src/test/java/org/apache/tez/dag/api/client/TestTimelineReaderFactory.java
new file mode 100644
index 0000000..4aff0ca
--- /dev/null
+++ 
b/tez-api/src/test/java/org/apache/tez/dag/api/client/TestTimelineReaderFactory.java
@@ -0,0 +1,91 @@
+/**
+ * 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.tez.dag.api.client;
+
+import static org.mockito.Mockito.mock;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authentication.client.ConnectionConfigurator;
+import org.apache.tez.dag.api.TezException;
+import 
org.apache.tez.dag.api.client.TimelineReaderFactory.TimelineReaderPseudoAuthenticatedStrategy;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestTimelineReaderFactory {
+
+  @Before
+  public void setup() {
+    // Disable tests if hadoop version is less than 2.4.0
+    // as Timeline is not supported in 2.2.x or 2.3.x
+    String hadoopVersion = System.getProperty("tez.hadoop.version");
+    Assume.assumeFalse(hadoopVersion.startsWith("2.2.") || 
hadoopVersion.startsWith("2.3."));
+  }
+
+  // ensure on hadoop 2.4 TimelinePseudoAuthenticatedStrategy is used.
+  @Test(timeout = 5000)
+  public void testShouldUsePseudoAuthStrategyForHadoop24() throws TezException 
{
+    String hadoopVersion = System.getProperty("tez.hadoop.version");
+    Assume.assumeTrue(hadoopVersion.startsWith("2.4.") || 
hadoopVersion.startsWith("2.5."));
+
+    String returnedClassName =
+        
TimelineReaderFactory.getTimelineReaderStrategy(mock(Configuration.class), 
false, 0)
+            .getClass()
+            .getCanonicalName();
+    Assert.assertEquals("should use pseudo auth on hadoop2.4",
+        
"org.apache.tez.dag.api.client.TimelineReaderFactory.TimelineReaderPseudoAuthenticatedStrategy",
+        returnedClassName);
+  }
+
+  // ensure on hadoop 2.6+ TimelineReaderTokenAuthenticatedStrategy is used.
+  @Test(timeout = 5000)
+  public void testShouldUseTokenDelegationAuthStrategyForHadoop26() throws 
TezException {
+    String hadoopVersion = System.getProperty("tez.hadoop.version");
+    Assume.assumeFalse(hadoopVersion.startsWith("2.2.") ||
+        hadoopVersion.startsWith("2.3.") ||
+            hadoopVersion.startsWith("2.4.") ||
+            hadoopVersion.startsWith("2.5."));
+
+    String returnedClassName =
+        
TimelineReaderFactory.getTimelineReaderStrategy(mock(Configuration.class), 
false, 0)
+            .getClass()
+            .getCanonicalName();
+    Assert.assertEquals("should use pseudo auth on hadoop2.4",
+        
"org.apache.tez.dag.api.client.TimelineReaderFactory.TimelineReaderTokenAuthenticatedStrategy",
+        returnedClassName);
+  }
+
+  @Test(timeout = 5000)
+  public void testPseudoAuthenticatorConnectionUrlShouldHaveUserName() throws 
Exception {
+    ConnectionConfigurator connConf = mock(ConnectionConfigurator.class);
+    
TimelineReaderPseudoAuthenticatedStrategy.PseudoAuthenticatedURLConnectionFactory
+        connectionFactory = new TimelineReaderPseudoAuthenticatedStrategy
+          .PseudoAuthenticatedURLConnectionFactory(connConf);
+    String inputUrl = "http://host:8080/path";;
+    String expectedUrl = inputUrl + "?user.name=" + 
UserGroupInformation.getCurrentUser().getShortUserName();
+    HttpURLConnection httpURLConnection = 
connectionFactory.getHttpURLConnection(new URL(inputUrl));
+    Assert.assertEquals(expectedUrl, httpURLConnection.getURL().toString());
+  }
+
+}

Reply via email to