KNOX-2890 - Prevent non-idempotent requests from failing over (#742)
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/ConfigurableHADispatch.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/ConfigurableHADispatch.java
index d731390..e42ab96 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/ConfigurableHADispatch.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/ConfigurableHADispatch.java
@@ -61,18 +61,20 @@
protected static final HaDispatchMessages LOG = MessagesFactory.get(HaDispatchMessages.class);
- private int maxFailoverAttempts = HaServiceConfigConstants.DEFAULT_MAX_FAILOVER_ATTEMPTS;
+ protected int maxFailoverAttempts = HaServiceConfigConstants.DEFAULT_MAX_FAILOVER_ATTEMPTS;
- private int failoverSleep = HaServiceConfigConstants.DEFAULT_FAILOVER_SLEEP;
+ protected int failoverSleep = HaServiceConfigConstants.DEFAULT_FAILOVER_SLEEP;
- private HaProvider haProvider;
+ protected HaProvider haProvider;
private static final Map<String, String> urlToHashLookup = new HashMap<>();
private static final Map<String, String> hashToUrlLookup = new HashMap<>();
+ protected static final List<String> nonIdempotentRequests = Arrays.asList("POST", "PATCH", "CONNECT");
private boolean loadBalancingEnabled = HaServiceConfigConstants.DEFAULT_LOAD_BALANCING_ENABLED;
private boolean stickySessionsEnabled = HaServiceConfigConstants.DEFAULT_STICKY_SESSIONS_ENABLED;
private boolean noFallbackEnabled = HaServiceConfigConstants.DEFAULT_NO_FALLBACK_ENABLED;
+ protected boolean failoverNonIdempotentRequestEnabled = HaServiceConfigConstants.DEFAULT_FAILOVER_NON_IDEMPOTENT;
private String stickySessionCookieName = HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME;
private List<String> disableLoadBalancingForUserAgents = Arrays.asList(HaServiceConfigConstants.DEFAULT_DISABLE_LB_USER_AGENTS);
@@ -97,6 +99,7 @@
maxFailoverAttempts = serviceConfig.getMaxFailoverAttempts();
failoverSleep = serviceConfig.getFailoverSleep();
loadBalancingEnabled = serviceConfig.isLoadBalancingEnabled();
+ failoverNonIdempotentRequestEnabled = serviceConfig.isFailoverNonIdempotentRequestEnabled();
/* enforce dependency */
stickySessionsEnabled = loadBalancingEnabled && serviceConfig.isStickySessionEnabled();
@@ -213,8 +216,14 @@
inboundResponse = executeOutboundRequest(outboundRequest);
writeOutboundResponse(outboundRequest, inboundRequest, outboundResponse, inboundResponse);
} catch ( IOException e ) {
- LOG.errorConnectingToServer(outboundRequest.getURI().toString(), e);
- failoverRequest(outboundRequest, inboundRequest, outboundResponse, inboundResponse, e);
+ /* if non-idempotent requests are not allowed to failover */
+ if(!failoverNonIdempotentRequestEnabled && nonIdempotentRequests.stream().anyMatch(outboundRequest.getMethod()::equalsIgnoreCase)) {
+ LOG.cannotFailoverNonIdempotentRequest(outboundRequest.getMethod(), e.toString());
+ throw e;
+ } else {
+ LOG.errorConnectingToServer(outboundRequest.getURI().toString(), e);
+ failoverRequest(outboundRequest, inboundRequest, outboundResponse, inboundResponse, e);
+ }
}
}
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/i18n/HaDispatchMessages.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/i18n/HaDispatchMessages.java
index 8aaa27d..004af14 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/i18n/HaDispatchMessages.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/i18n/HaDispatchMessages.java
@@ -53,4 +53,7 @@
@Message(level = MessageLevel.ERROR, text = "Unsupported encoding, cause: {0}")
void unsupportedEncodingException(String cause);
+
+ @Message(level = MessageLevel.ERROR, text = "Request is non-idempotent {0}, failover prevented, to allow non-idempotent requests to failover set 'failoverNonIdempotentRequestEnabled=true' in HA config. Cause {1}")
+ void cannotFailoverNonIdempotentRequest(String method, String cause);
}
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/HaServiceConfig.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/HaServiceConfig.java
index 1a5706b..801d312 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/HaServiceConfig.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/HaServiceConfig.java
@@ -62,4 +62,9 @@
void setDisableStickySessionForUserAgents(String disableStickySessionForUserAgents);
String getStickySessionDisabledUserAgents();
+
+ boolean isFailoverNonIdempotentRequestEnabled();
+
+ void setFailoverNonIdempotentRequestEnabled(boolean failoverNonIdempotentRequestEnabled);
+
}
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultHaServiceConfig.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultHaServiceConfig.java
index 5a23006..baa08c3 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultHaServiceConfig.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultHaServiceConfig.java
@@ -35,6 +35,8 @@
private boolean isNoFallbackEnabled = DEFAULT_NO_FALLBACK_ENABLED;
+ private boolean failoverNonIdempotentRequestEnabled = DEFAULT_FAILOVER_NON_IDEMPOTENT;
+
private String stickySessionCookieName = DEFAULT_STICKY_SESSION_COOKIE_NAME;
private String zookeeperEnsemble;
@@ -156,4 +158,15 @@
public String getStickySessionDisabledUserAgents() {
return disableStickySessionForUserAgents;
}
+
+ @Override
+ public boolean isFailoverNonIdempotentRequestEnabled() {
+ return failoverNonIdempotentRequestEnabled;
+ }
+
+ @Override
+ public void setFailoverNonIdempotentRequestEnabled(
+ boolean failoverNonIdempotentRequestEnabled) {
+ this.failoverNonIdempotentRequestEnabled = failoverNonIdempotentRequestEnabled;
+ }
}
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorConstants.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorConstants.java
index 21edd17..05d813e 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorConstants.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorConstants.java
@@ -53,4 +53,12 @@
String STICKY_SESSION_COOKIE_NAME = "stickySessionCookieName";
String DISABLE_LB_USER_AGENTS = "disableLoadBalancingForUserAgents";
+
+ /**
+ * For non-idempotent requests such as
+ * POST, PATCH, CONNECT
+ * should we failover?
+ * default is false (no).
+ */
+ String FAILOVER_NON_IDEMPOTENT = "failoverNonIdempotentRequestEnabled";
}
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorFactory.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorFactory.java
index 88d927b..bf5886f 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorFactory.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorFactory.java
@@ -42,10 +42,62 @@
final boolean loadBalancingEnabled = Boolean.parseBoolean(configMap.getOrDefault(CONFIG_LOAD_BALANCING_ENABLED, Boolean.toString(DEFAULT_LOAD_BALANCING_ENABLED)));
final boolean noFallbackEnabled = Boolean.parseBoolean(configMap.getOrDefault(CONFIG_NO_FALLBACK_ENABLED, Boolean.toString(DEFAULT_NO_FALLBACK_ENABLED)));
final String stickySessionCookieName = configMap.getOrDefault(STICKY_SESSION_COOKIE_NAME, DEFAULT_STICKY_SESSION_COOKIE_NAME);
-
+ final boolean failoverNonIdempotentRequestEnabled = Boolean.parseBoolean(configMap.getOrDefault(FAILOVER_NON_IDEMPOTENT, Boolean.toString(DEFAULT_FAILOVER_NON_IDEMPOTENT)));
final String disableLoadBalancingForUserAgentsConfig = configMap.getOrDefault(DISABLE_LB_USER_AGENTS, DEFAULT_DISABLE_LB_USER_AGENTS);
return createServiceConfig(serviceName, enabled, maxFailoverAttempts, failoverSleep, zookeeperEnsemble, zookeeperNamespace, stickySessionsEnabled, loadBalancingEnabled,
- stickySessionCookieName, noFallbackEnabled, disableLoadBalancingForUserAgentsConfig);
+ stickySessionCookieName, noFallbackEnabled, disableLoadBalancingForUserAgentsConfig, failoverNonIdempotentRequestEnabled);
+ }
+
+ public static HaServiceConfig createServiceConfig(String serviceName, String enabledValue,
+ String maxFailoverAttemptsValue, String failoverSleepValue,
+ String zookeeperEnsemble, String zookeeperNamespace,
+ String loadBalancingEnabledValue, String stickySessionsEnabledValue,
+ String stickySessionCookieNameValue, String noFallbackEnabledValue,
+ String disableLoadBalancingForUserAgentsValue, String failoverNonIdempotentRequestEnabledValue) {
+
+ boolean enabled = DEFAULT_ENABLED;
+ int maxFailoverAttempts = DEFAULT_MAX_FAILOVER_ATTEMPTS;
+ int failoverSleep = DEFAULT_FAILOVER_SLEEP;
+ boolean stickySessionsEnabled = DEFAULT_STICKY_SESSIONS_ENABLED;
+ boolean loadBalancingEnabled = DEFAULT_LOAD_BALANCING_ENABLED;
+ boolean noFallbackEnabled = DEFAULT_NO_FALLBACK_ENABLED;
+ String stickySessionCookieName = DEFAULT_STICKY_SESSION_COOKIE_NAME;
+ String disableLoadBalancingForUserAgentsConfig = DEFAULT_DISABLE_LB_USER_AGENTS;
+ boolean failoverNonIdempotentRequestEnabled = DEFAULT_FAILOVER_NON_IDEMPOTENT;
+
+ if (enabledValue != null && !enabledValue.trim().isEmpty()) {
+ enabled = Boolean.parseBoolean(enabledValue);
+ }
+ if (maxFailoverAttemptsValue != null && !maxFailoverAttemptsValue.trim().isEmpty()) {
+ maxFailoverAttempts = Integer.parseInt(maxFailoverAttemptsValue);
+ }
+ if (failoverSleepValue != null && !failoverSleepValue.trim().isEmpty()) {
+ failoverSleep = Integer.parseInt(failoverSleepValue);
+ }
+ if (stickySessionsEnabledValue != null && !stickySessionsEnabledValue.trim().isEmpty()) {
+ stickySessionsEnabled = Boolean.parseBoolean(stickySessionsEnabledValue);
+ }
+ if (loadBalancingEnabledValue != null && !loadBalancingEnabledValue.trim().isEmpty()) {
+ loadBalancingEnabled = Boolean.parseBoolean(loadBalancingEnabledValue);
+ }
+ if (stickySessionCookieNameValue != null && !stickySessionCookieNameValue.trim().isEmpty()) {
+ stickySessionCookieName = stickySessionCookieNameValue;
+ }
+ if (noFallbackEnabledValue != null && !noFallbackEnabledValue.trim().isEmpty()) {
+ noFallbackEnabled = Boolean.parseBoolean(noFallbackEnabledValue);
+ }
+ if(StringUtils.isNotBlank(disableLoadBalancingForUserAgentsValue)) {
+ disableLoadBalancingForUserAgentsConfig = disableLoadBalancingForUserAgentsValue;
+ }
+
+ if (failoverNonIdempotentRequestEnabledValue != null && !failoverNonIdempotentRequestEnabledValue.trim().isEmpty()) {
+ failoverNonIdempotentRequestEnabled = Boolean.parseBoolean(failoverNonIdempotentRequestEnabledValue);
+ }
+
+ return createServiceConfig(serviceName, enabled, maxFailoverAttempts, failoverSleep, zookeeperEnsemble, zookeeperNamespace, stickySessionsEnabled, loadBalancingEnabled,
+ stickySessionCookieName, noFallbackEnabled, disableLoadBalancingForUserAgentsConfig, failoverNonIdempotentRequestEnabled);
+
+
}
/**
@@ -70,49 +122,18 @@
String loadBalancingEnabledValue, String stickySessionsEnabledValue,
String stickySessionCookieNameValue, String noFallbackEnabledValue,
String disableLoadBalancingForUserAgentsValue) {
- boolean enabled = DEFAULT_ENABLED;
- int maxFailoverAttempts = DEFAULT_MAX_FAILOVER_ATTEMPTS;
- int failoverSleep = DEFAULT_FAILOVER_SLEEP;
- boolean stickySessionsEnabled = DEFAULT_STICKY_SESSIONS_ENABLED;
- boolean loadBalancingEnabled = DEFAULT_LOAD_BALANCING_ENABLED;
- boolean noFallbackEnabled = DEFAULT_NO_FALLBACK_ENABLED;
- String stickySessionCookieName = DEFAULT_STICKY_SESSION_COOKIE_NAME;
- String disableLoadBalancingForUserAgentsConfig = DEFAULT_DISABLE_LB_USER_AGENTS;
- if (enabledValue != null && !enabledValue.trim().isEmpty()) {
- enabled = Boolean.parseBoolean(enabledValue);
- }
- if (maxFailoverAttemptsValue != null && !maxFailoverAttemptsValue.trim().isEmpty()) {
- maxFailoverAttempts = Integer.parseInt(maxFailoverAttemptsValue);
- }
- if (failoverSleepValue != null && !failoverSleepValue.trim().isEmpty()) {
- failoverSleep = Integer.parseInt(failoverSleepValue);
- }
- if (stickySessionsEnabledValue != null && !stickySessionsEnabledValue.trim().isEmpty()) {
- stickySessionsEnabled = Boolean.parseBoolean(stickySessionsEnabledValue);
- }
- if (loadBalancingEnabledValue != null && !loadBalancingEnabledValue.trim().isEmpty()) {
- loadBalancingEnabled = Boolean.parseBoolean(loadBalancingEnabledValue);
- }
- if (stickySessionCookieNameValue != null && !stickySessionCookieNameValue.trim().isEmpty()) {
- stickySessionCookieName = stickySessionCookieNameValue;
- }
- if (noFallbackEnabledValue != null && !noFallbackEnabledValue.trim().isEmpty()) {
- noFallbackEnabled = Boolean.parseBoolean(noFallbackEnabledValue);
- }
- if(StringUtils.isNotBlank(disableLoadBalancingForUserAgentsValue)) {
- disableLoadBalancingForUserAgentsConfig = disableLoadBalancingForUserAgentsValue;
- }
- return createServiceConfig(serviceName, enabled, maxFailoverAttempts, failoverSleep, zookeeperEnsemble, zookeeperNamespace, stickySessionsEnabled, loadBalancingEnabled,
- stickySessionCookieName, noFallbackEnabled, disableLoadBalancingForUserAgentsConfig);
+ return createServiceConfig(serviceName, enabledValue, maxFailoverAttemptsValue, failoverSleepValue, zookeeperEnsemble, zookeeperNamespace, loadBalancingEnabledValue, stickySessionsEnabledValue,
+ stickySessionCookieNameValue, noFallbackEnabledValue, disableLoadBalancingForUserAgentsValue, Boolean.toString(DEFAULT_FAILOVER_NON_IDEMPOTENT));
}
- private static DefaultHaServiceConfig createServiceConfig(final String serviceName, final boolean enabled,
+ public static DefaultHaServiceConfig createServiceConfig(final String serviceName, final boolean enabled,
final int maxFailoverAttempts, final int failoverSleepValue,
final String zookeeperEnsemble, final String zookeeperNamespace,
final boolean stickySessionsEnabled, final boolean loadBalancingEnabled,
final String stickySessionCookieName,
- final boolean noFallbackEnabled, final String disableStickySessionForUserAgents) {
+ final boolean noFallbackEnabled, final String disableStickySessionForUserAgents,
+ final boolean failoverNonIdempotentRequestEnabled) {
DefaultHaServiceConfig serviceConfig = new DefaultHaServiceConfig(serviceName);
serviceConfig.setEnabled(enabled);
serviceConfig.setMaxFailoverAttempts(maxFailoverAttempts);
@@ -124,6 +145,7 @@
serviceConfig.setStickySessionCookieName(stickySessionCookieName);
serviceConfig.setNoFallbackEnabled(noFallbackEnabled);
serviceConfig.setDisableStickySessionForUserAgents(disableStickySessionForUserAgents);
+ serviceConfig.setFailoverNonIdempotentRequestEnabled(failoverNonIdempotentRequestEnabled);
return serviceConfig;
}
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorManager.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorManager.java
index b7ed878..b24f758 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorManager.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorManager.java
@@ -69,6 +69,7 @@
if(config.getStickySessionDisabledUserAgents() != null && !config.getStickySessionDisabledUserAgents().isEmpty()) {
serviceElement.setAttribute(DISABLE_LB_USER_AGENTS, config.getStickySessionDisabledUserAgents());
}
+ serviceElement.setAttribute(FAILOVER_NON_IDEMPOTENT, Boolean.toString(config.isFailoverNonIdempotentRequestEnabled()));
root.appendChild(serviceElement);
}
}
@@ -99,7 +100,8 @@
element.getAttribute(ENABLE_STICKY_SESSIONS),
element.getAttribute(STICKY_SESSION_COOKIE_NAME),
element.getAttribute(ENABLE_NO_FALLBACK),
- element.getAttribute(DISABLE_LB_USER_AGENTS));
+ element.getAttribute(DISABLE_LB_USER_AGENTS),
+ element.getAttribute(FAILOVER_NON_IDEMPOTENT));
descriptor.addServiceConfig(config);
}
}
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaServiceConfigConstants.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaServiceConfigConstants.java
index 092ce0a..21149c5 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaServiceConfigConstants.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/HaServiceConfigConstants.java
@@ -41,6 +41,14 @@
String STICKY_SESSION_COOKIE_NAME = "stickySessionCookieName";
/**
+ * For non-idempotent requests such as
+ * POST, PATCH, CONNECT
+ * should we failover?
+ * default is false (no).
+ */
+ String FAILOVER_NON_IDEMPOTENT = "failoverNonIdempotentRequestEnabled";
+
+ /**
* Disable loadbalancing feature based on user agent.
* The code will look for "contains" match
*/
@@ -58,6 +66,8 @@
boolean DEFAULT_NO_FALLBACK_ENABLED = false;
+ boolean DEFAULT_FAILOVER_NON_IDEMPOTENT = false;
+
String DEFAULT_STICKY_SESSION_COOKIE_NAME = "KNOX_BACKEND";
String DEFAULT_DISABLE_LB_USER_AGENTS = "ClouderaODBCDriverforApacheHive";
diff --git a/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/dispatch/DefaultHaDispatchTest.java b/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/dispatch/DefaultHaDispatchTest.java
index 1545f0a..8cf39e3 100644
--- a/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/dispatch/DefaultHaDispatchTest.java
+++ b/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/dispatch/DefaultHaDispatchTest.java
@@ -604,6 +604,399 @@
doTestFailoverStickyOnFallbackOff(true, sessionCookieLast);
}
+ /**
+ * Test whether non-idempotent requests are prevented from failing over
+ *
+ * KNOX-2890
+ */
+ @Test
+ public void testFailoverForNonIdempotentRequests() throws Exception {
+ Cookie [] sessionCookieFirst = new Cookie[3];
+ sessionCookieFirst[0] = new Cookie(HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME + "-" + "OOZIE",
+ "59973e253ae20de796c6ef413608ec1c80fca24310a4cbdecc0ff97aeea55745");
+ sessionCookieFirst[1] = new Cookie("Test1", "Test1");
+ sessionCookieFirst[2] = new Cookie("Test2", "Test2");
+
+ final boolean enableLoadBalancing = true; // load-balancing is required for sticky sessions to be enabled
+ final boolean enableStickySession = true;
+ final boolean noFallback = false;
+ final boolean failoverNonIdempotentRequestEnabled = false;
+
+ final String serviceName = "OOZIE";
+
+ HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+ descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName,
+ true,
+ 1,
+ 1000,
+ null,
+ null,
+ enableStickySession,
+ enableLoadBalancing,
+ null,
+ noFallback,
+ null,
+ failoverNonIdempotentRequestEnabled));
+
+ final HaProvider provider = new DefaultHaProvider(descriptor);
+ final URI uri1 = new URI( "http://host1.valid" );
+ final URI uri2 = new URI( "http://host2.valid" );
+ final ArrayList<String> urlList = new ArrayList<>();
+ urlList.add(uri1.toString());
+ urlList.add(uri2.toString());
+ provider.addHaService(serviceName, urlList);
+ FilterConfig filterConfig = EasyMock.createNiceMock(FilterConfig.class);
+ ServletContext servletContext = EasyMock.createNiceMock(ServletContext.class);
+
+ EasyMock.expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes();
+ EasyMock.expect(servletContext.getAttribute(HaServletContextListener.PROVIDER_ATTRIBUTE_NAME)).andReturn(provider).anyTimes();
+
+ BasicHttpParams params = new BasicHttpParams();
+
+ HttpUriRequest outboundRequest = EasyMock.createNiceMock(HttpRequestBase.class);
+ EasyMock.expect(outboundRequest.getMethod()).andReturn( "POST" ).anyTimes();
+ EasyMock.expect(outboundRequest.getURI()).andReturn( uri1 ).anyTimes();
+ EasyMock.expect(outboundRequest.getParams()).andReturn( params ).anyTimes();
+ // Capture the last request URI to be set on the request
+ Capture<URI> requestURICapture = EasyMock.newCapture(CaptureType.LAST);
+ ((HttpRequestBase) outboundRequest).setURI(EasyMock.capture(requestURICapture));
+ EasyMock.expectLastCall().anyTimes();
+
+ /* backend request */
+ HttpServletRequest inboundRequest = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(inboundRequest.getRequestURL()).andReturn( new StringBuffer(uri2.toString()) ).once();
+ EasyMock.expect(inboundRequest.getAttribute("dispatch.ha.failover.counter")).andReturn(new AtomicInteger(0)).once();
+ EasyMock.expect(inboundRequest.getAttribute("dispatch.ha.failover.counter")).andReturn(new AtomicInteger(1)).once();
+
+
+ /* backend response */
+ CloseableHttpResponse inboundResponse = EasyMock.createNiceMock(CloseableHttpResponse.class);
+ final StatusLine statusLine = EasyMock.createNiceMock(StatusLine.class);
+ final HttpEntity entity = EasyMock.createNiceMock(HttpEntity.class);
+ final Header header = EasyMock.createNiceMock(Header.class);
+ final ServletContext context = EasyMock.createNiceMock(ServletContext.class);
+ final GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+ final ByteArrayInputStream backendResponse = new ByteArrayInputStream("knox-backend".getBytes(StandardCharsets.UTF_8));
+
+ EasyMock.expect(inboundResponse.getStatusLine()).andReturn(statusLine).anyTimes();
+ EasyMock.expect(statusLine.getStatusCode()).andReturn(HttpStatus.SC_OK).anyTimes();
+ EasyMock.expect(inboundResponse.getEntity()).andReturn(entity).anyTimes();
+ EasyMock.expect(inboundResponse.getAllHeaders()).andReturn(new Header[0]).anyTimes();
+ EasyMock.expect(inboundRequest.getServletContext()).andReturn(context).anyTimes();
+ EasyMock.expect(entity.getContent()).andReturn(backendResponse).anyTimes();
+ EasyMock.expect(entity.getContentType()).andReturn(header).anyTimes();
+ EasyMock.expect(header.getElements()).andReturn(new HeaderElement[]{}).anyTimes();
+ EasyMock.expect(entity.getContentLength()).andReturn(4L).anyTimes();
+ EasyMock.expect(context.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(config).anyTimes();
+
+ HttpServletResponse outboundResponse = EasyMock.createNiceMock(HttpServletResponse.class);
+ // Capture the status code when it is set on the response
+ Capture<Integer> statusCodeCapture = EasyMock.newCapture(CaptureType.FIRST);
+
+ outboundResponse.setStatus(EasyMock.captureInt(statusCodeCapture));
+
+ EasyMock.expectLastCall().once();
+ EasyMock.expect(outboundResponse.getOutputStream())
+ .andAnswer((IAnswer<SynchronousServletOutputStreamAdapter>) () -> new SynchronousServletOutputStreamAdapter() {
+ @Override
+ public void write( int b ) throws IOException {
+ throw new IOException( "unreachable-host" ); // Fail-over condition
+ }
+ }).once();
+
+ CloseableHttpClient mockHttpClient = EasyMock.createNiceMock(CloseableHttpClient.class);
+ EasyMock.expect(mockHttpClient.execute(outboundRequest)).andReturn(inboundResponse).anyTimes();
+
+ EasyMock.replay(filterConfig,
+ servletContext,
+ outboundRequest,
+ inboundRequest,
+ outboundResponse,
+ mockHttpClient,
+ inboundResponse,
+ statusLine,
+ entity,
+ header,
+ context,
+ config);
+
+ Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
+ ConfigurableHADispatch dispatch = new ConfigurableHADispatch();
+ dispatch.setHttpClient(mockHttpClient);
+ dispatch.setHaProvider(provider);
+ dispatch.setServiceRole(serviceName);
+ dispatch.init();
+ try {
+ dispatch.executeRequestWrapper(outboundRequest, inboundRequest, outboundResponse);
+ // If the call succeeds then test failed
+ throw new Exception("Expected the request to NOT failover");
+ } catch (IOException e) {
+ Assert.assertTrue("Expected the request to NOT failover", e.toString().equalsIgnoreCase("java.io.IOException: unreachable-host"));
+ }
+
+ }
+
+ /**
+ * Test that non-idempotent requests fail over when
+ * failoverNonIdempotentRequestEnabled=true
+ *
+ * KNOX-2890
+ */
+ @Test
+ public void testFailoverWhenNonIdempotentRequestsEnabled() throws Exception {
+ Cookie [] sessionCookieFirst = new Cookie[3];
+ sessionCookieFirst[0] = new Cookie(HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME + "-" + "OOZIE",
+ "59973e253ae20de796c6ef413608ec1c80fca24310a4cbdecc0ff97aeea55745");
+ sessionCookieFirst[1] = new Cookie("Test1", "Test1");
+ sessionCookieFirst[2] = new Cookie("Test2", "Test2");
+
+ final boolean enableLoadBalancing = true; // load-balancing is required for sticky sessions to be enabled
+ final boolean enableStickySession = true;
+ final boolean noFallback = false;
+ final boolean failoverNonIdempotentRequestEnabled = true;
+
+ final String serviceName = "OOZIE";
+
+ HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+ descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName,
+ true,
+ 1,
+ 1000,
+ null,
+ null,
+ enableStickySession,
+ enableLoadBalancing,
+ null,
+ noFallback,
+ null,
+ failoverNonIdempotentRequestEnabled));
+
+ final HaProvider provider = new DefaultHaProvider(descriptor);
+ final URI uri1 = new URI( "http://host1.valid" );
+ final URI uri2 = new URI( "http://host2.valid" );
+ final ArrayList<String> urlList = new ArrayList<>();
+ urlList.add(uri1.toString());
+ urlList.add(uri2.toString());
+ provider.addHaService(serviceName, urlList);
+ FilterConfig filterConfig = EasyMock.createNiceMock(FilterConfig.class);
+ ServletContext servletContext = EasyMock.createNiceMock(ServletContext.class);
+
+ EasyMock.expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes();
+ EasyMock.expect(servletContext.getAttribute(HaServletContextListener.PROVIDER_ATTRIBUTE_NAME)).andReturn(provider).anyTimes();
+
+ BasicHttpParams params = new BasicHttpParams();
+
+ HttpUriRequest outboundRequest = EasyMock.createNiceMock(HttpRequestBase.class);
+ EasyMock.expect(outboundRequest.getMethod()).andReturn( "POST" ).anyTimes();
+ EasyMock.expect(outboundRequest.getURI()).andReturn( uri1 ).anyTimes();
+ EasyMock.expect(outboundRequest.getParams()).andReturn( params ).anyTimes();
+ // Capture the last request URI to be set on the request
+ Capture<URI> requestURICapture = EasyMock.newCapture(CaptureType.LAST);
+ ((HttpRequestBase) outboundRequest).setURI(EasyMock.capture(requestURICapture));
+ EasyMock.expectLastCall().anyTimes();
+
+ /* backend request */
+ HttpServletRequest inboundRequest = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(inboundRequest.getRequestURL()).andReturn( new StringBuffer(uri2.toString()) ).once();
+ EasyMock.expect(inboundRequest.getAttribute("dispatch.ha.failover.counter")).andReturn(new AtomicInteger(0)).once();
+ EasyMock.expect(inboundRequest.getAttribute("dispatch.ha.failover.counter")).andReturn(new AtomicInteger(1)).once();
+
+
+ /* backend response */
+ CloseableHttpResponse inboundResponse = EasyMock.createNiceMock(CloseableHttpResponse.class);
+ final StatusLine statusLine = EasyMock.createNiceMock(StatusLine.class);
+ final HttpEntity entity = EasyMock.createNiceMock(HttpEntity.class);
+ final Header header = EasyMock.createNiceMock(Header.class);
+ final ServletContext context = EasyMock.createNiceMock(ServletContext.class);
+ final GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+ final ByteArrayInputStream backendResponse = new ByteArrayInputStream("knox-backend".getBytes(StandardCharsets.UTF_8));
+
+ EasyMock.expect(inboundResponse.getStatusLine()).andReturn(statusLine).anyTimes();
+ EasyMock.expect(statusLine.getStatusCode()).andReturn(HttpStatus.SC_OK).anyTimes();
+ EasyMock.expect(inboundResponse.getEntity()).andReturn(entity).anyTimes();
+ EasyMock.expect(inboundResponse.getAllHeaders()).andReturn(new Header[0]).anyTimes();
+ EasyMock.expect(inboundRequest.getServletContext()).andReturn(context).anyTimes();
+ EasyMock.expect(entity.getContent()).andReturn(backendResponse).anyTimes();
+ EasyMock.expect(entity.getContentType()).andReturn(header).anyTimes();
+ EasyMock.expect(header.getElements()).andReturn(new HeaderElement[]{}).anyTimes();
+ EasyMock.expect(entity.getContentLength()).andReturn(4L).anyTimes();
+ EasyMock.expect(context.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(config).anyTimes();
+
+ HttpServletResponse outboundResponse = EasyMock.createNiceMock(HttpServletResponse.class);
+ // Capture the status code when it is set on the response
+ Capture<Integer> statusCodeCapture = EasyMock.newCapture(CaptureType.FIRST);
+
+ outboundResponse.setStatus(EasyMock.captureInt(statusCodeCapture));
+
+ EasyMock.expectLastCall().once();
+ EasyMock.expect(outboundResponse.getOutputStream())
+ .andAnswer((IAnswer<SynchronousServletOutputStreamAdapter>) () -> new SynchronousServletOutputStreamAdapter() {
+ @Override
+ public void write( int b ) throws IOException {
+ throw new IOException( "unreachable-host" ); // Fail-over condition
+ }
+ }).once();
+
+ CloseableHttpClient mockHttpClient = EasyMock.createNiceMock(CloseableHttpClient.class);
+ EasyMock.expect(mockHttpClient.execute(outboundRequest)).andReturn(inboundResponse).anyTimes();
+
+ EasyMock.replay(filterConfig,
+ servletContext,
+ outboundRequest,
+ inboundRequest,
+ outboundResponse,
+ mockHttpClient,
+ inboundResponse,
+ statusLine,
+ entity,
+ header,
+ context,
+ config);
+
+ Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
+ ConfigurableHADispatch dispatch = new ConfigurableHADispatch();
+ dispatch.setHttpClient(mockHttpClient);
+ dispatch.setHaProvider(provider);
+ dispatch.setServiceRole(serviceName);
+ dispatch.init();
+
+ dispatch.executeRequestWrapper(outboundRequest, inboundRequest, outboundResponse);
+
+ Assert.assertEquals("Expected the request to have failed-over to the alternate host.", uri2, requestURICapture.getValue());
+ Assert.assertEquals("Expected the failed-over request to succeed.", HttpStatus.SC_OK, statusCodeCapture.getValue().intValue());
+
+ }
+
+ /**
+ * Test that idempotent requests (GET) can fail over.
+ *
+ * KNOX-2890
+ */
+ @Test
+ public void testFailoverForIdempotentRequest() throws Exception {
+ Cookie [] sessionCookieFirst = new Cookie[3];
+ sessionCookieFirst[0] = new Cookie(HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME + "-" + "OOZIE",
+ "59973e253ae20de796c6ef413608ec1c80fca24310a4cbdecc0ff97aeea55745");
+ sessionCookieFirst[1] = new Cookie("Test1", "Test1");
+ sessionCookieFirst[2] = new Cookie("Test2", "Test2");
+
+ final boolean enableLoadBalancing = true; // load-balancing is required for sticky sessions to be enabled
+ final boolean enableStickySession = true;
+ final boolean noFallback = false;
+ final boolean failoverNonIdempotentRequestEnabled = false;
+
+ final String serviceName = "OOZIE";
+
+ HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+ descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName,
+ true,
+ 1,
+ 1000,
+ null,
+ null,
+ enableStickySession,
+ enableLoadBalancing,
+ null,
+ noFallback,
+ null,
+ failoverNonIdempotentRequestEnabled));
+
+ final HaProvider provider = new DefaultHaProvider(descriptor);
+ final URI uri1 = new URI( "http://host1.valid" );
+ final URI uri2 = new URI( "http://host2.valid" );
+ final ArrayList<String> urlList = new ArrayList<>();
+ urlList.add(uri1.toString());
+ urlList.add(uri2.toString());
+ provider.addHaService(serviceName, urlList);
+ FilterConfig filterConfig = EasyMock.createNiceMock(FilterConfig.class);
+ ServletContext servletContext = EasyMock.createNiceMock(ServletContext.class);
+
+ EasyMock.expect(filterConfig.getServletContext()).andReturn(servletContext).anyTimes();
+ EasyMock.expect(servletContext.getAttribute(HaServletContextListener.PROVIDER_ATTRIBUTE_NAME)).andReturn(provider).anyTimes();
+
+ BasicHttpParams params = new BasicHttpParams();
+
+ HttpUriRequest outboundRequest = EasyMock.createNiceMock(HttpRequestBase.class);
+ EasyMock.expect(outboundRequest.getMethod()).andReturn( "GET" ).anyTimes();
+ EasyMock.expect(outboundRequest.getURI()).andReturn( uri1 ).anyTimes();
+ EasyMock.expect(outboundRequest.getParams()).andReturn( params ).anyTimes();
+ // Capture the last request URI to be set on the request
+ Capture<URI> requestURICapture = EasyMock.newCapture(CaptureType.LAST);
+ ((HttpRequestBase) outboundRequest).setURI(EasyMock.capture(requestURICapture));
+ EasyMock.expectLastCall().anyTimes();
+
+ /* backend request */
+ HttpServletRequest inboundRequest = EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(inboundRequest.getRequestURL()).andReturn( new StringBuffer(uri2.toString()) ).once();
+ EasyMock.expect(inboundRequest.getAttribute("dispatch.ha.failover.counter")).andReturn(new AtomicInteger(0)).once();
+ EasyMock.expect(inboundRequest.getAttribute("dispatch.ha.failover.counter")).andReturn(new AtomicInteger(1)).once();
+
+
+ /* backend response */
+ CloseableHttpResponse inboundResponse = EasyMock.createNiceMock(CloseableHttpResponse.class);
+ final StatusLine statusLine = EasyMock.createNiceMock(StatusLine.class);
+ final HttpEntity entity = EasyMock.createNiceMock(HttpEntity.class);
+ final Header header = EasyMock.createNiceMock(Header.class);
+ final ServletContext context = EasyMock.createNiceMock(ServletContext.class);
+ final GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+ final ByteArrayInputStream backendResponse = new ByteArrayInputStream("knox-backend".getBytes(StandardCharsets.UTF_8));
+
+ EasyMock.expect(inboundResponse.getStatusLine()).andReturn(statusLine).anyTimes();
+ EasyMock.expect(statusLine.getStatusCode()).andReturn(HttpStatus.SC_OK).anyTimes();
+ EasyMock.expect(inboundResponse.getEntity()).andReturn(entity).anyTimes();
+ EasyMock.expect(inboundResponse.getAllHeaders()).andReturn(new Header[0]).anyTimes();
+ EasyMock.expect(inboundRequest.getServletContext()).andReturn(context).anyTimes();
+ EasyMock.expect(entity.getContent()).andReturn(backendResponse).anyTimes();
+ EasyMock.expect(entity.getContentType()).andReturn(header).anyTimes();
+ EasyMock.expect(header.getElements()).andReturn(new HeaderElement[]{}).anyTimes();
+ EasyMock.expect(entity.getContentLength()).andReturn(4L).anyTimes();
+ EasyMock.expect(context.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(config).anyTimes();
+
+ HttpServletResponse outboundResponse = EasyMock.createNiceMock(HttpServletResponse.class);
+ // Capture the status code when it is set on the response
+ Capture<Integer> statusCodeCapture = EasyMock.newCapture(CaptureType.FIRST);
+
+ outboundResponse.setStatus(EasyMock.captureInt(statusCodeCapture));
+
+ EasyMock.expectLastCall().once();
+ EasyMock.expect(outboundResponse.getOutputStream())
+ .andAnswer((IAnswer<SynchronousServletOutputStreamAdapter>) () -> new SynchronousServletOutputStreamAdapter() {
+ @Override
+ public void write( int b ) throws IOException {
+ throw new IOException( "unreachable-host" ); // Fail-over condition
+ }
+ }).once();
+
+ CloseableHttpClient mockHttpClient = EasyMock.createNiceMock(CloseableHttpClient.class);
+ EasyMock.expect(mockHttpClient.execute(outboundRequest)).andReturn(inboundResponse).anyTimes();
+
+ EasyMock.replay(filterConfig,
+ servletContext,
+ outboundRequest,
+ inboundRequest,
+ outboundResponse,
+ mockHttpClient,
+ inboundResponse,
+ statusLine,
+ entity,
+ header,
+ context,
+ config);
+
+ Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
+ ConfigurableHADispatch dispatch = new ConfigurableHADispatch();
+ dispatch.setHttpClient(mockHttpClient);
+ dispatch.setHaProvider(provider);
+ dispatch.setServiceRole(serviceName);
+ dispatch.init();
+
+ dispatch.executeRequestWrapper(outboundRequest, inboundRequest, outboundResponse);
+
+ Assert.assertEquals("Expected the request to have failed-over to the alternate host.", uri2, requestURICapture.getValue());
+ Assert.assertEquals("Expected the failed-over request to succeed.", HttpStatus.SC_OK, statusCodeCapture.getValue().intValue());
+
+ }
+
private void doTestFailoverStickyOnFallbackOff(final Boolean withCookie)
throws Exception {
doTestFailoverStickyOnFallbackOff(withCookie, null);
diff --git a/gateway-service-webhdfs/src/main/java/org/apache/knox/gateway/hdfs/dispatch/AbstractHdfsHaDispatch.java b/gateway-service-webhdfs/src/main/java/org/apache/knox/gateway/hdfs/dispatch/AbstractHdfsHaDispatch.java
index 3e808fe..0c7865c 100644
--- a/gateway-service-webhdfs/src/main/java/org/apache/knox/gateway/hdfs/dispatch/AbstractHdfsHaDispatch.java
+++ b/gateway-service-webhdfs/src/main/java/org/apache/knox/gateway/hdfs/dispatch/AbstractHdfsHaDispatch.java
@@ -17,15 +17,13 @@
*/
package org.apache.knox.gateway.hdfs.dispatch;
+import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.BufferedHttpEntity;
-import org.apache.knox.gateway.config.Configure;
import org.apache.knox.gateway.filter.AbstractGatewayFilter;
-import org.apache.knox.gateway.ha.provider.HaProvider;
-import org.apache.knox.gateway.ha.provider.HaServiceConfig;
-import org.apache.knox.gateway.ha.provider.impl.HaServiceConfigConstants;
+import org.apache.knox.gateway.ha.dispatch.ConfigurableHADispatch;
import org.apache.knox.gateway.hdfs.i18n.WebHdfsMessages;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
@@ -38,13 +36,10 @@
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;
-public abstract class AbstractHdfsHaDispatch extends HdfsHttpClientDispatch {
+public abstract class AbstractHdfsHaDispatch extends ConfigurableHADispatch {
private static final String FAILOVER_COUNTER_ATTRIBUTE = "dispatch.ha.failover.counter";
private static final WebHdfsMessages LOG = MessagesFactory.get(WebHdfsMessages.class);
- private int maxFailoverAttempts = HaServiceConfigConstants.DEFAULT_MAX_FAILOVER_ATTEMPTS;
- private int failoverSleep = HaServiceConfigConstants.DEFAULT_FAILOVER_SLEEP;
- private HaProvider haProvider;
public AbstractHdfsHaDispatch() throws ServletException {
super();
@@ -53,23 +48,10 @@
@Override
public void init() {
super.init();
- if (haProvider != null) {
- HaServiceConfig serviceConfig = haProvider.getHaDescriptor().getServiceConfig(getResourceRole());
- maxFailoverAttempts = serviceConfig.getMaxFailoverAttempts();
- failoverSleep = serviceConfig.getFailoverSleep();
- }
}
- public HaProvider getHaProvider() {
- return haProvider;
- }
-
abstract String getResourceRole();
- @Configure
- public void setHaProvider(HaProvider haProvider) {
- this.haProvider = haProvider;
- }
@Override
protected void executeRequest(HttpUriRequest outboundRequest, HttpServletRequest inboundRequest, HttpServletResponse outboundResponse) throws IOException {
@@ -77,18 +59,21 @@
try {
inboundResponse = executeOutboundRequest(outboundRequest);
writeOutboundResponse(outboundRequest, inboundRequest, outboundResponse, inboundResponse);
- } catch (StandbyException e) {
- LOG.errorReceivedFromStandbyNode(e);
- failoverRequest(outboundRequest, inboundRequest, outboundResponse, inboundResponse, e);
- } catch (SafeModeException e) {
- LOG.errorReceivedFromSafeModeNode(e);
- failoverRequest(outboundRequest, inboundRequest, outboundResponse, inboundResponse, e);
- } catch (IOException e) {
- LOG.errorConnectingToServer(outboundRequest.getURI().toString(), e);
- failoverRequest(outboundRequest, inboundRequest, outboundResponse, inboundResponse, e);
+ } catch (StandbyException | SafeModeException | IOException e) {
+ /* if non-idempotent requests are not allowed to failover */
+ if(!failoverNonIdempotentRequestEnabled && nonIdempotentRequests.stream().anyMatch(outboundRequest.getMethod()::equalsIgnoreCase)) {
+ LOG.cannotFailoverNonIdempotentRequest(outboundRequest.getMethod(), e.toString());
+ throw e;
+ } else {
+ printExceptionLogMessage(e, outboundRequest.getURI().toString());
+ failoverRequest(outboundRequest, inboundRequest, outboundResponse,
+ inboundResponse, e);
+ }
}
}
+
+
/**
* Checks for specific outbound response codes/content to trigger a retry or failover
*/
@@ -110,7 +95,8 @@
super.writeOutboundResponse(outboundRequest, inboundRequest, outboundResponse, inboundResponse);
}
- private void failoverRequest(HttpUriRequest outboundRequest, HttpServletRequest inboundRequest, HttpServletResponse outboundResponse, HttpResponse inboundResponse, Exception exception) throws IOException {
+ @Override
+ protected void failoverRequest(HttpUriRequest outboundRequest, HttpServletRequest inboundRequest, HttpServletResponse outboundResponse, HttpResponse inboundResponse, Exception exception) throws IOException {
LOG.failedToConnectTo(outboundRequest.getURI().toString());
AtomicInteger counter = (AtomicInteger) inboundRequest.getAttribute(FAILOVER_COUNTER_ATTRIBUTE);
if (counter == null) {
@@ -142,4 +128,33 @@
}
}
}
+
+ /**
+ * This method ensures that the request InputStream is not acquired
+ * prior to a dispatch to a component such as a namenode that doesn't
+ * the request body. The side effect of this is that the client does
+ * not get a 100 continue from Knox which will trigger the client to
+ * send the entire payload before redirect to the target component
+ * like a datanode and have to send it again.
+ */
+ @Override
+ protected HttpEntity createRequestEntity(HttpServletRequest request)
+ throws IOException {
+ return null;
+ }
+
+ /**
+ * Helper method that prints descriptive log messages about the exception thrown.
+ * @param e Exception
+ * @param uri outbound uri
+ */
+ private void printExceptionLogMessage(final Exception e, String uri) {
+ if(e instanceof StandbyException) {
+ LOG.errorReceivedFromStandbyNode(e);
+ } else if(e instanceof SafeModeException) {
+ LOG.errorReceivedFromSafeModeNode(e);
+ } else {
+ LOG.errorConnectingToServer(uri, e);
+ }
+ }
}
diff --git a/gateway-service-webhdfs/src/main/java/org/apache/knox/gateway/hdfs/dispatch/HdfsHttpClientDispatch.java b/gateway-service-webhdfs/src/main/java/org/apache/knox/gateway/hdfs/dispatch/HdfsHttpClientDispatch.java
index 9958d47..43adf3b 100644
--- a/gateway-service-webhdfs/src/main/java/org/apache/knox/gateway/hdfs/dispatch/HdfsHttpClientDispatch.java
+++ b/gateway-service-webhdfs/src/main/java/org/apache/knox/gateway/hdfs/dispatch/HdfsHttpClientDispatch.java
@@ -17,8 +17,8 @@
*/
package org.apache.knox.gateway.hdfs.dispatch;
-import org.apache.knox.gateway.dispatch.ConfigurableDispatch;
import org.apache.http.HttpEntity;
+import org.apache.knox.gateway.dispatch.ConfigurableDispatch;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
diff --git a/gateway-service-webhdfs/src/test/java/org/apache/knox/gateway/hdfs/dispatch/WebHdfsHaDispatchTest.java b/gateway-service-webhdfs/src/test/java/org/apache/knox/gateway/hdfs/dispatch/WebHdfsHaDispatchTest.java
index 0bd5e33..3e66d43 100644
--- a/gateway-service-webhdfs/src/test/java/org/apache/knox/gateway/hdfs/dispatch/WebHdfsHaDispatchTest.java
+++ b/gateway-service-webhdfs/src/test/java/org/apache/knox/gateway/hdfs/dispatch/WebHdfsHaDispatchTest.java
@@ -93,6 +93,7 @@
CloseableHttpClient client = builder.build();
dispatch.setHttpClient(client);
dispatch.setHaProvider(provider);
+ dispatch.setServiceRole(serviceName);
dispatch.init();
long startTime = System.currentTimeMillis();
try {
diff --git a/gateway-test/src/test/java/org/apache/knox/gateway/WebHdfsHaFuncTest.java b/gateway-test/src/test/java/org/apache/knox/gateway/WebHdfsHaFuncTest.java
index 388560e..401ad20 100644
--- a/gateway-test/src/test/java/org/apache/knox/gateway/WebHdfsHaFuncTest.java
+++ b/gateway-test/src/test/java/org/apache/knox/gateway/WebHdfsHaFuncTest.java
@@ -142,7 +142,7 @@
.addTag("name").addText("HaProvider")
.addTag("param")
.addTag("name").addText("WEBHDFS")
- .addTag("value").addText("maxFailoverAttempts=3;failoverSleep=15;enabled=true").gotoParent()
+ .addTag("value").addText("maxFailoverAttempts=5;failoverSleep=15;enabled=true;failoverNonIdempotentRequestEnabled=true").gotoParent()
.gotoRoot()
.addTag("service")
.addTag("role").addText("WEBHDFS")