This is an automated email from the ASF dual-hosted git repository.
abstractdog pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push:
new 1afd305ba60 HIVE-26670: Track every single HTTP request between
beeline and hs2 (#3710) (Laszlo Bodor reviewed by Ayush Saxena, Chris Nauroth)
1afd305ba60 is described below
commit 1afd305ba602bdeac0e5608ce858ef5c2dab6e43
Author: Bodor Laszlo <[email protected]>
AuthorDate: Tue Nov 1 13:47:21 2022 +0100
HIVE-26670: Track every single HTTP request between beeline and hs2 (#3710)
(Laszlo Bodor reviewed by Ayush Saxena, Chris Nauroth)
---
.../src/main/resources/beeline-log4j2.properties | 6 ++-
.../org/apache/hadoop/hive/conf/Constants.java | 2 +
.../java/org/apache/hive/jdbc/HiveConnection.java | 33 ++++++++++++--
.../hive/jdbc/HttpDefaultResponseInterceptor.java | 42 ++++++++++++++++++
.../hive/jdbc/HttpRequestInterceptorBase.java | 35 ++++++++++++++-
.../hive/jdbc/HttpResponseInterceptorBase.java | 26 +++++++++++
jdbc/src/java/org/apache/hive/jdbc/Utils.java | 2 +
.../org/apache/hive/jdbc/TestHiveConnection.java | 21 ++++++++-
.../hive/jdbc/TestHttpRequestInterceptor.java | 51 ++++++++++++++++++++++
.../hive/service/cli/thrift/ThriftHttpServlet.java | 11 ++++-
10 files changed, 220 insertions(+), 9 deletions(-)
diff --git a/beeline/src/main/resources/beeline-log4j2.properties
b/beeline/src/main/resources/beeline-log4j2.properties
index 103d72253f4..1300609ac87 100644
--- a/beeline/src/main/resources/beeline-log4j2.properties
+++ b/beeline/src/main/resources/beeline-log4j2.properties
@@ -33,12 +33,16 @@ appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yy/MM/dd HH:mm:ss} [%t]: %p %c{2}: %m%n
# list of all loggers
-loggers = HiveConnection
+loggers = HiveConnection, HiveJDBC
# HiveConnection logs useful info for dynamic service discovery
logger.HiveConnection.name = org.apache.hive.jdbc.HiveConnection
logger.HiveConnection.level = INFO
+# all jdbc class logs can be useful while investigating connection issues,
connection tracking
+logger.HiveJDBC.name = org.apache.hive.jdbc
+logger.HiveJDBC.level = INFO
+
# root logger
rootLogger.level = ${sys:hive.log.level}
rootLogger.appenderRefs = root
diff --git a/common/src/java/org/apache/hadoop/hive/conf/Constants.java
b/common/src/java/org/apache/hadoop/hive/conf/Constants.java
index decdc648a6c..c923c2bcce3 100644
--- a/common/src/java/org/apache/hadoop/hive/conf/Constants.java
+++ b/common/src/java/org/apache/hadoop/hive/conf/Constants.java
@@ -103,4 +103,6 @@ public class Constants {
public static final Pattern COMPACTION_POOLS_PATTERN =
Pattern.compile("hive\\.compactor\\.worker\\.(.*)\\.threads");
public static final String HIVE_COMPACTOR_WORKER_POOL =
"hive.compactor.worker.pool";
+ public static final String HTTP_HEADER_REQUEST_TRACK = "Request-Track";
+ public static final String TIME_POSTFIX_REQUEST_TRACK = "_TIME";
}
diff --git a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
index 4a6fb7c423d..c91416a02b7 100644
--- a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
+++ b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
@@ -65,7 +65,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReentrantLock;
@@ -120,7 +119,6 @@ import
org.apache.hive.service.rpc.thrift.TRenewDelegationTokenResp;
import org.apache.hive.service.rpc.thrift.TSessionHandle;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.NoHttpResponseException;
import org.apache.http.HttpStatus;
@@ -144,6 +142,7 @@ import
org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.Args;
+import org.apache.thrift.TBaseHelper;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.THttpClient;
@@ -151,6 +150,7 @@ import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.function.Supplier;
/**
* HiveConnection.
@@ -559,7 +559,7 @@ public class HiveConnection implements java.sql.Connection {
CookieStore cookieStore = isCookieEnabled ? new BasicCookieStore() : null;
HttpClientBuilder httpClientBuilder = null;
// Request interceptor for any request pre-processing logic
- HttpRequestInterceptor requestInterceptor;
+ HttpRequestInterceptorBase requestInterceptor;
Map<String, String> additionalHttpHeaders = new HashMap<String, String>();
Map<String, String> customCookies = new HashMap<String, String>();
@@ -752,8 +752,12 @@ public class HiveConnection implements java.sql.Connection
{
httpClientBuilder
.setRedirectStrategy(new
HiveJdbcSamlRedirectStrategy(browserClient));
}
+
+ requestInterceptor.setRequestTrackingEnabled(isRequestTrackingEnabled());
+
// Add the request interceptor to the client builder
- httpClientBuilder.addInterceptorFirst(requestInterceptor);
+
httpClientBuilder.addInterceptorFirst(requestInterceptor.sessionId(getSessionId()));
+ httpClientBuilder.addInterceptorLast(new HttpDefaultResponseInterceptor());
// Add an interceptor to add in an XSRF header
httpClientBuilder.addInterceptorLast(new XsrfHttpRequestInterceptor());
@@ -813,6 +817,27 @@ public class HiveConnection implements java.sql.Connection
{
return httpClientBuilder.build();
}
+ private boolean isRequestTrackingEnabled() {
+ return
Boolean.parseBoolean(sessConfMap.get(JdbcConnectionParams.JDBC_PARAM_REQUEST_TRACK));
+ }
+
+ /**
+ * Creates a sessionId Supplier for interceptors. When interceptors are
instantiated,
+ * there is no session yet (sessHandle is null) so this Supplier can take
care
+ * of the sessionId in a lazy way.
+ */
+ private Supplier<String> getSessionId() {
+ Supplier<String> sessionId = () -> {
+ if (sessHandle == null) {
+ return "NO_SESSION";
+ }
+ StringBuilder b = new StringBuilder();
+ TBaseHelper.toString(sessHandle.getSessionId().bufferForGuid(), b);
+ return b.toString().replaceAll("\\s", "");
+ };
+ return sessionId;
+ }
+
private String getJWT() {
String jwtCredential = getJWTStringFromSession();
if (jwtCredential == null || jwtCredential.isEmpty()) {
diff --git
a/jdbc/src/java/org/apache/hive/jdbc/HttpDefaultResponseInterceptor.java
b/jdbc/src/java/org/apache/hive/jdbc/HttpDefaultResponseInterceptor.java
new file mode 100644
index 00000000000..94e5b9125d0
--- /dev/null
+++ b/jdbc/src/java/org/apache/hive/jdbc/HttpDefaultResponseInterceptor.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hive.jdbc;
+
+import java.io.IOException;
+
+import org.apache.hadoop.hive.conf.Constants;
+import org.apache.hadoop.util.Time;
+import org.apache.http.HttpException;
+import org.apache.http.HttpResponse;
+import org.apache.http.protocol.HttpContext;
+
+public class HttpDefaultResponseInterceptor extends
HttpResponseInterceptorBase {
+
+ @Override
+ public void process(HttpResponse response, HttpContext context) throws
HttpException, IOException {
+ String trackHeader = (String)
context.getAttribute(Constants.HTTP_HEADER_REQUEST_TRACK);
+ if (trackHeader == null) {
+ return;
+ }
+ String trackTimeHeader = trackHeader +
Constants.TIME_POSTFIX_REQUEST_TRACK;
+ long elapsed = Time.monotonicNow() - (long)
context.getAttribute(trackTimeHeader);
+ LOG.info("Response to {} in {} ms", trackHeader, elapsed);
+ context.removeAttribute(Constants.HTTP_HEADER_REQUEST_TRACK);
+ context.removeAttribute(trackTimeHeader);
+ }
+}
diff --git a/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java
b/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java
index eeaa48a5d11..862d299643e 100644
--- a/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java
+++ b/jdbc/src/java/org/apache/hive/jdbc/HttpRequestInterceptorBase.java
@@ -19,21 +19,33 @@
package org.apache.hive.jdbc;
import java.io.IOException;
+import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+import org.apache.hadoop.hive.conf.Constants;
+import org.apache.hadoop.util.Time;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.CookieStore;
import org.apache.http.protocol.HttpContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public abstract class HttpRequestInterceptorBase implements
HttpRequestInterceptor {
+ protected final Logger LOG = LoggerFactory.getLogger(getClass());
+
CookieStore cookieStore;
boolean isCookieEnabled;
String cookieName;
boolean isSSL;
Map<String, String> additionalHeaders;
Map<String, String> customCookies;
+ private Supplier<String> sessionId = null;
+ private boolean requestTrackingEnabled;
+ private final AtomicLong requestTrackCounter = new AtomicLong();
// Abstract function to add HttpAuth Header
protected abstract void addHttpAuthHeader(HttpRequest httpRequest,
HttpContext httpContext)
@@ -45,7 +57,7 @@ public abstract class HttpRequestInterceptorBase implements
HttpRequestIntercept
this.isCookieEnabled = (cs != null);
this.cookieName = cn;
this.isSSL = isSSL;
- this.additionalHeaders = additionalHeaders;
+ this.additionalHeaders = additionalHeaders == null ? new HashMap<>() :
additionalHeaders;
this.customCookies = customCookies;
}
@@ -77,6 +89,14 @@ public abstract class HttpRequestInterceptorBase implements
HttpRequestIntercept
if (isCookieEnabled) {
httpContext.setAttribute(Utils.HIVE_SERVER2_RETRY_KEY,
Utils.HIVE_SERVER2_CONST_FALSE);
}
+
+ if (requestTrackingEnabled) {
+ String trackHeader = getNewTrackHeader();
+ LOG.info("{}:{}", Constants.HTTP_HEADER_REQUEST_TRACK, trackHeader);
+ additionalHeaders.put(Constants.HTTP_HEADER_REQUEST_TRACK,
trackHeader);
+ httpContext.setAttribute(Constants.HTTP_HEADER_REQUEST_TRACK,
trackHeader);
+ httpContext.setAttribute(trackHeader +
Constants.TIME_POSTFIX_REQUEST_TRACK, Time.monotonicNow());
+ }
// Insert the additional http headers
if (additionalHeaders != null) {
for (Map.Entry<String, String> entry : additionalHeaders.entrySet()) {
@@ -102,4 +122,17 @@ public abstract class HttpRequestInterceptorBase
implements HttpRequestIntercept
throw new HttpException(e.getMessage(), e);
}
}
+
+ protected String getNewTrackHeader() {
+ return String.format("HIVE_%s_%020d", sessionId.get(),
requestTrackCounter.incrementAndGet());
+ }
+
+ public HttpRequestInterceptor sessionId(Supplier<String> sessionId) {
+ this.sessionId = sessionId;
+ return this;
+ }
+
+ public void setRequestTrackingEnabled(boolean requestTrackingEnabled) {
+ this.requestTrackingEnabled = requestTrackingEnabled;
+ }
}
diff --git
a/jdbc/src/java/org/apache/hive/jdbc/HttpResponseInterceptorBase.java
b/jdbc/src/java/org/apache/hive/jdbc/HttpResponseInterceptorBase.java
new file mode 100644
index 00000000000..6036a12d38f
--- /dev/null
+++ b/jdbc/src/java/org/apache/hive/jdbc/HttpResponseInterceptorBase.java
@@ -0,0 +1,26 @@
+/*
+ * 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.hive.jdbc;
+
+import org.apache.http.HttpResponseInterceptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class HttpResponseInterceptorBase implements
HttpResponseInterceptor {
+ protected final Logger LOG = LoggerFactory.getLogger(getClass());
+}
diff --git a/jdbc/src/java/org/apache/hive/jdbc/Utils.java
b/jdbc/src/java/org/apache/hive/jdbc/Utils.java
index c1be6a52df4..a855d4e2a5d 100644
--- a/jdbc/src/java/org/apache/hive/jdbc/Utils.java
+++ b/jdbc/src/java/org/apache/hive/jdbc/Utils.java
@@ -156,6 +156,8 @@ public class Utils {
static final String DEFAULT_COOKIE_NAMES_HS2 = "hive.server2.auth";
// The http header prefix for additional headers which have to be appended
to the request
static final String HTTP_HEADER_PREFIX = "http.header.";
+ // Request tracking
+ static final String JDBC_PARAM_REQUEST_TRACK = "requestTrack";
// Set the fetchSize
static final String FETCH_SIZE = "fetchSize";
static final String INIT_FILE = "initFile";
diff --git a/jdbc/src/test/org/apache/hive/jdbc/TestHiveConnection.java
b/jdbc/src/test/org/apache/hive/jdbc/TestHiveConnection.java
index bcd2608e1ba..71abe784488 100644
--- a/jdbc/src/test/org/apache/hive/jdbc/TestHiveConnection.java
+++ b/jdbc/src/test/org/apache/hive/jdbc/TestHiveConnection.java
@@ -18,15 +18,17 @@
package org.apache.hive.jdbc;
+import java.io.IOException;
+import java.sql.SQLException;
+
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.Credentials;
import org.apache.hadoop.security.token.Token;
+import org.apache.hive.jdbc.Utils.JdbcConnectionParams;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
-import java.io.IOException;
-
public class TestHiveConnection {
private static final String EXISTING_TOKEN = "ExistingToken";
@@ -57,4 +59,19 @@ public class TestHiveConnection {
String tokenStr = fetcher.getTokenFromCredential(creds, EXISTING_TOKEN);
Assert.assertEquals("Token string form is not as expected.",
EXPECTED_TOKEN_STRING_FORM, tokenStr);
}
+
+ @Test
+ public void testHiveConnectionParameters() throws SQLException,
ZooKeeperHiveClientException {
+ JdbcConnectionParams params = Utils.parseURL(
+
"jdbc:hive2://hello.host:10002/default;transportMode=http;httpPath=cliservice;socketTimeout=60;requestTrack=true;");
+
+ Assert.assertEquals("hello.host", params.getHost());
+ Assert.assertEquals("default", params.getDbName());
+ Assert.assertEquals(10002, params.getPort());
+
+ Assert.assertEquals("http",
params.getSessionVars().get(JdbcConnectionParams.TRANSPORT_MODE));
+ Assert.assertEquals("cliservice",
params.getSessionVars().get(JdbcConnectionParams.HTTP_PATH));
+ Assert.assertEquals("60",
params.getSessionVars().get(JdbcConnectionParams.SOCKET_TIMEOUT));
+ Assert.assertEquals("true",
params.getSessionVars().get(JdbcConnectionParams.JDBC_PARAM_REQUEST_TRACK));
+ }
}
diff --git a/jdbc/src/test/org/apache/hive/jdbc/TestHttpRequestInterceptor.java
b/jdbc/src/test/org/apache/hive/jdbc/TestHttpRequestInterceptor.java
new file mode 100644
index 00000000000..4c56aa6e7b1
--- /dev/null
+++ b/jdbc/src/test/org/apache/hive/jdbc/TestHttpRequestInterceptor.java
@@ -0,0 +1,51 @@
+/*
+ * 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.hive.jdbc;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.apache.hadoop.hive.conf.Constants;
+import org.apache.http.HttpException;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.BasicHttpContext;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestHttpRequestInterceptor {
+
+ @Test
+ public void testRequestTrackHeader() throws HttpException, IOException {
+ HttpRequestInterceptorBase requestInterceptor = getInterceptor();
+ requestInterceptor.setRequestTrackingEnabled(true);
+ requestInterceptor.sessionId(() -> "sessionId");
+ requestInterceptor.process(new BasicHttpRequest("POST", "uri"), new
BasicHttpContext());
+
+
Assert.assertTrue(requestInterceptor.additionalHeaders.containsKey(Constants.HTTP_HEADER_REQUEST_TRACK));
+ Assert.assertEquals("HIVE_sessionId_00000000000000000001",
+
requestInterceptor.additionalHeaders.get(Constants.HTTP_HEADER_REQUEST_TRACK));
+ }
+
+ private HttpRequestInterceptorBase getInterceptor() {
+ HttpRequestInterceptorBase requestInterceptor = new
HttpBasicAuthInterceptor("user", "pass", new BasicCookieStore(),
+ "cookieName", false, new HashMap<>(), new HashMap<>());
+ return requestInterceptor;
+ }
+}
diff --git
a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
index bbb74e0c7ca..0c192f45ca4 100644
--- a/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
+++ b/service/src/java/org/apache/hive/service/cli/thrift/ThriftHttpServlet.java
@@ -24,7 +24,6 @@ import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
-import java.text.ParseException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
@@ -45,6 +44,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
+import org.apache.hadoop.hive.conf.Constants;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.shims.HadoopShims.KerberosNameShim;
@@ -153,6 +153,8 @@ public class ThriftHttpServlet extends TServlet {
String clientIpAddress;
boolean requireNewCookie = false;
+ logTrackingHeaderIfAny(request);
+
try {
if
(hiveConf.getBoolean(ConfVars.HIVE_SERVER2_XSRF_FILTER_ENABLED.varname,false)){
boolean continueProcessing =
Utils.doXsrfFilter(request,response,null,null);
@@ -310,6 +312,13 @@ public class ThriftHttpServlet extends TServlet {
}
}
+ private void logTrackingHeaderIfAny(HttpServletRequest request) {
+ if (request.getHeader(Constants.HTTP_HEADER_REQUEST_TRACK) != null) {
+ String requestTrackHeader =
request.getHeader(Constants.HTTP_HEADER_REQUEST_TRACK);
+ LOG.info("{}:{}", Constants.HTTP_HEADER_REQUEST_TRACK,
requestTrackHeader);
+ }
+ }
+
private String validateJWT(HttpServletRequest request, HttpServletResponse
response)
throws HttpAuthenticationException {
Preconditions.checkState(jwtValidator != null, "JWT validator should have
been set");