This is an automated email from the ASF dual-hosted git repository.
more pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 48d04f3 KNOX-2545 - Account for loadbalancing and stickysession
corner cases. (#417)
48d04f3 is described below
commit 48d04f3053e3ab92c2aab6bd4e75cf4086efa154
Author: Sandeep Moré <[email protected]>
AuthorDate: Wed Mar 17 05:57:30 2021 -0400
KNOX-2545 - Account for loadbalancing and stickysession corner cases. (#417)
* KNOX-2545 - Account for loadbalancing and stickysession corner cases.
---
.../ha/dispatch/ConfigurableHADispatch.java | 37 +-
.../gateway/ha/dispatch/DefaultHaDispatchTest.java | 419 ++++++++++++++++++++-
2 files changed, 437 insertions(+), 19 deletions(-)
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 6c98abb..a7849a3 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
@@ -68,8 +68,8 @@ public class ConfigurableHADispatch extends
ConfigurableDispatch {
private static final Map<String, String> urlToHashLookup = new HashMap<>();
private static final Map<String, String> hashToUrlLookup = new HashMap<>();
- private boolean stickySessionsEnabled =
HaServiceConfigConstants.DEFAULT_STICKY_SESSIONS_ENABLED;
private boolean loadBalancingEnabled =
HaServiceConfigConstants.DEFAULT_LOAD_BALANCING_ENABLED;
+ private boolean stickySessionsEnabled =
HaServiceConfigConstants.DEFAULT_STICKY_SESSIONS_ENABLED;
private boolean noFallbackEnabled =
HaServiceConfigConstants.DEFAULT_NO_FALLBACK_ENABLED;
private String stickySessionCookieName =
HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME;
@@ -81,10 +81,14 @@ public class ConfigurableHADispatch extends
ConfigurableDispatch {
HaServiceConfig serviceConfig =
haProvider.getHaDescriptor().getServiceConfig(getServiceRole());
maxFailoverAttempts = serviceConfig.getMaxFailoverAttempts();
failoverSleep = serviceConfig.getFailoverSleep();
- stickySessionsEnabled = serviceConfig.isStickySessionEnabled();
loadBalancingEnabled = serviceConfig.isLoadBalancingEnabled();
- noFallbackEnabled = serviceConfig.isNoFallbackEnabled();
- stickySessionCookieName = serviceConfig.getStickySessionCookieName();
+
+ /* enforce dependency */
+ stickySessionsEnabled = loadBalancingEnabled &&
serviceConfig.isStickySessionEnabled();
+ noFallbackEnabled = stickySessionsEnabled &&
serviceConfig.isNoFallbackEnabled();
+ if(stickySessionsEnabled) {
+ stickySessionCookieName = serviceConfig.getStickySessionCookieName();
+ }
setupUrlHashLookup();
}
@@ -120,12 +124,25 @@ public class ConfigurableHADispatch extends
ConfigurableDispatch {
}
executeRequest(outboundRequest, inboundRequest, outboundResponse);
/**
- * Load balance when
- * 1. loadbalancing is enabled and sticky sessions are off
- * 2. sticky sessions are enabled and it is a new session (no url in
cookie)
+ * 1. Load balance when loadbalancing is enabled.
+ * 2. Loadbalance only when sticky session is enabled but cookie not
detected
+ * i.e. when loadbalancing is enabled every request that does not
have BACKEND cookie
+ * needs to be loadbalanced. If a request has BACKEND coookie and
Loadbalance=on then
+ * there should be no loadbalancing.
*/
- if ( (!backendURI.isPresent() && stickySessionsEnabled) ||
loadBalancingEnabled) {
+ if (loadBalancingEnabled) {
+ /* check sticky session enabled */
+ if(stickySessionsEnabled) {
+ /* loadbalance only when sticky session enabled and no backend url
cookie */
+ if(!backendURI.isPresent()) {
+ haProvider.makeNextActiveURLAvailable(getServiceRole());
+ } else{
+ /* sticky session enabled and backend url cookie is valid no need
to loadbalance */
+ /* do nothing */
+ }
+ } else {
haProvider.makeNextActiveURLAvailable(getServiceRole());
+ }
}
}
@@ -148,7 +165,7 @@ public class ConfigurableHADispatch extends
ConfigurableDispatch {
}
private Optional<URI> setBackendfromHaCookie(HttpUriRequest outboundRequest,
HttpServletRequest inboundRequest) {
- if (stickySessionsEnabled && inboundRequest.getCookies() != null) {
+ if (loadBalancingEnabled && stickySessionsEnabled &&
inboundRequest.getCookies() != null) {
for (Cookie cookie : inboundRequest.getCookies()) {
if (stickySessionCookieName.equals(cookie.getName())) {
String backendURLHash = cookie.getValue();
@@ -207,7 +224,7 @@ public class ConfigurableHADispatch extends
ConfigurableDispatch {
protected void failoverRequest(HttpUriRequest outboundRequest,
HttpServletRequest inboundRequest, HttpServletResponse outboundResponse,
HttpResponse inboundResponse, Exception exception) throws IOException {
/* check for a case where no fallback is configured */
- if(noFallbackEnabled && stickySessionsEnabled) {
+ if(stickySessionsEnabled && noFallbackEnabled) {
LOG.noFallbackError();
outboundResponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, "Service
connection error, HA failover disabled");
return;
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 7e51904..2584957 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
@@ -17,7 +17,14 @@
*/
package org.apache.knox.gateway.ha.dispatch;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.HttpClients;
+import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.ha.provider.HaDescriptor;
import org.apache.knox.gateway.ha.provider.HaProvider;
import org.apache.knox.gateway.ha.provider.HaServletContextListener;
@@ -36,10 +43,13 @@ import org.junit.Test;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
+import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -89,7 +99,7 @@ public class DefaultHaDispatchTest {
}).once();
EasyMock.replay(filterConfig, servletContext, outboundRequest,
inboundRequest, outboundResponse);
Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
- DefaultHaDispatch dispatch = new DefaultHaDispatch();
+ ConfigurableHADispatch dispatch = new ConfigurableHADispatch();
HttpClientBuilder builder = HttpClientBuilder.create();
CloseableHttpClient client = builder.build();
dispatch.setHttpClient(client);
@@ -98,7 +108,7 @@ public class DefaultHaDispatchTest {
dispatch.init();
long startTime = System.currentTimeMillis();
try {
- dispatch.executeRequest(outboundRequest, inboundRequest,
outboundResponse);
+ dispatch.executeRequestWrapper(outboundRequest, inboundRequest,
outboundResponse);
} catch (IOException e) {
//this is expected after the failover limit is reached
}
@@ -108,8 +118,13 @@ public class DefaultHaDispatchTest {
Assert.assertTrue(elapsedTime > 1000);
}
+ /**
+ * Test failover when loadbalancing=false, sticky=true, nofallback=true.
+ * should failover.
+ * @throws Exception
+ */
@Test
- public void testStickyFailoverNoFallback() throws Exception {
+ public void testNoLoadbalancingStickyFailoverNoFallback() throws Exception {
String serviceName = "OOZIE";
HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName,
"true", "1", "1000", null, null, null, "true", null, "true"));
@@ -152,7 +167,7 @@ public class DefaultHaDispatchTest {
}).once();
EasyMock.replay(filterConfig, servletContext, outboundRequest,
inboundRequest, outboundResponse);
Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
- DefaultHaDispatch dispatch = new DefaultHaDispatch();
+ ConfigurableHADispatch dispatch = new ConfigurableHADispatch();
HttpClientBuilder builder = HttpClientBuilder.create();
CloseableHttpClient client = builder.build();
dispatch.setHttpClient(client);
@@ -160,12 +175,12 @@ public class DefaultHaDispatchTest {
dispatch.setServiceRole(serviceName);
dispatch.init();
try {
- dispatch.executeRequest(outboundRequest, inboundRequest,
outboundResponse);
+ dispatch.executeRequestWrapper(outboundRequest, inboundRequest,
outboundResponse);
} catch (IOException e) {
//this is expected after the failover limit is reached
}
- /* since fallback did not happen */
- Assert.assertNotEquals(uri2.toString(),
provider.getActiveURL(serviceName));
+ /* since fallback happens */
+ Assert.assertEquals(uri2.toString(), provider.getActiveURL(serviceName));
}
/**
@@ -218,7 +233,7 @@ public class DefaultHaDispatchTest {
}).once();
EasyMock.replay(filterConfig, servletContext, outboundRequest,
inboundRequest, outboundResponse);
Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
- DefaultHaDispatch dispatch = new DefaultHaDispatch();
+ ConfigurableHADispatch dispatch = new ConfigurableHADispatch();
HttpClientBuilder builder = HttpClientBuilder.create();
CloseableHttpClient client = builder.build();
dispatch.setHttpClient(client);
@@ -227,7 +242,7 @@ public class DefaultHaDispatchTest {
dispatch.init();
long startTime = System.currentTimeMillis();
try {
- dispatch.executeRequest(outboundRequest, inboundRequest,
outboundResponse);
+ dispatch.executeRequestWrapper(outboundRequest, inboundRequest,
outboundResponse);
} catch (IOException e) {
//this is expected after the failover limit is reached
}
@@ -237,6 +252,392 @@ public class DefaultHaDispatchTest {
Assert.assertTrue(elapsedTime > 1000);
}
+ /**
+ * Test the case where loadbalancing is off and sticky session is on
+ * Expected behavior: When loadbalncing is off sticky sessions on is
+ * that there should be no url loadbalancing
+ * @throws Exception
+ */
+ @Test
+ public void testLoadbalancingOffStickyOn() throws Exception {
+ String serviceName = "OOZIE";
+ HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+
descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName,
"true", "1", "1000", null, null, null, "true", null, null));
+ HaProvider provider = new DefaultHaProvider(descriptor);
+ URI uri1 = new URI( "http://host1.valid" );
+ URI uri2 = new URI( "http://host2.valid" );
+ 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();
+
+ /* 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);
+ EasyMock.expect(outboundResponse.getOutputStream()).andAnswer( new
IAnswer<SynchronousServletOutputStreamAdapter>() {
+ @Override
+ public SynchronousServletOutputStreamAdapter answer() {
+ return new SynchronousServletOutputStreamAdapter() {
+ @Override
+ public void write( int b ) throws IOException {
+ /* do nothing */
+ }
+ };
+ }
+ }).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);
+ } catch (IOException e) {
+ //this is expected after the failover limit is reached
+ }
+ /* make sure the url is not ladbalanced since fallback did not happen */
+ Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
+ }
+
+ /**
+ * Test the case where loadbalancing is on
+ * Expected behavior: When loadbalncing is on then urls should loadbalance
+ * @throws Exception
+ */
+ @Test
+ public void testLoadbalancingOn() throws Exception {
+ String serviceName = "OOZIE";
+ HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+
descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName,
"true", "1", "1000", null, null, "true", null, null, null));
+ HaProvider provider = new DefaultHaProvider(descriptor);
+ URI uri1 = new URI( "http://host1.valid" );
+ URI uri2 = new URI( "http://host2.valid" );
+ 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();
+
+ /* 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);
+ EasyMock.expect(outboundResponse.getOutputStream()).andAnswer( new
IAnswer<SynchronousServletOutputStreamAdapter>() {
+ @Override
+ public SynchronousServletOutputStreamAdapter answer() {
+ return new SynchronousServletOutputStreamAdapter() {
+ @Override
+ public void write( int b ) throws IOException {
+ /* do nothing */
+ }
+ };
+ }
+ }).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);
+ } catch (IOException e) {
+ //this is expected after the failover limit is reached
+ }
+ /* make sure the url is ladbalanced */
+ Assert.assertEquals(uri2.toString(), provider.getActiveURL(serviceName));
+ }
+
+ /**
+ * Test the case where loadbalancing is on and sticky session is on
+ * Expected behavior: When loadbalncing is on and sticky session
+ * is on = urls should loadbalance with sticky session
+ * @throws Exception
+ */
+ @Test
+ public void testLoadbalancingOnStickyOn() throws Exception {
+ String serviceName = "OOZIE";
+ HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+
descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName,
"true", "1", "1000", null, null, "true", "true", null, null));
+ HaProvider provider = new DefaultHaProvider(descriptor);
+ URI uri1 = new URI( "http://host1.valid" );
+ URI uri2 = new URI( "http://host2.valid" );
+ 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();
+
+ /* 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);
+ EasyMock.expect(outboundResponse.getOutputStream()).andAnswer( new
IAnswer<SynchronousServletOutputStreamAdapter>() {
+ @Override
+ public SynchronousServletOutputStreamAdapter answer() {
+ return new SynchronousServletOutputStreamAdapter() {
+ @Override
+ public void write( int b ) throws IOException {
+ /* do nothing */
+ }
+ };
+ }
+ }).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);
+ } catch (IOException e) {
+ //this is expected after the failover limit is reached
+ }
+ /* make sure the url is loadbalanced */
+ Assert.assertEquals(uri2.toString(), provider.getActiveURL(serviceName));
+ }
+
+ /**
+ * Test the case where sticky session is on (and loadbalancing is on)
+ * Expected behavior: When
+ * @throws Exception
+ */
+ @Test
+ public void testStickyOn() throws Exception {
+ String serviceName = "OOZIE";
+ HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+
descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName,
"true", "1", "1000", null, null, "true", "true", null, null));
+ HaProvider provider = new DefaultHaProvider(descriptor);
+ URI uri1 = new URI( "http://host1.valid" );
+ URI uri2 = new URI( "http://host2.valid" );
+ 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( uri2 ).anyTimes();
+ EasyMock.expect(outboundRequest.getParams()).andReturn( params
).anyTimes();
+
+ /* backend request with cookie for url2 */
+ //http://host2.valid =
59973e253ae20de796c6ef413608ec1c80fca24310a4cbdecc0ff97aeea55745
+ Cookie[] cookie = new Cookie[] { new
Cookie("KNOX_BACKEND-OOZIE","59973e253ae20de796c6ef413608ec1c80fca24310a4cbdecc0ff97aeea55745")};
+ HttpServletRequest inboundRequest =
EasyMock.createNiceMock(HttpServletRequest.class);
+ EasyMock.expect(inboundRequest.getRequestURL()).andReturn( new
StringBuffer(uri2.toString()) ).once();
+ EasyMock.expect(inboundRequest.getCookies()).andReturn( cookie
).anyTimes();
+
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);
+ EasyMock.expect(outboundResponse.getOutputStream()).andAnswer( new
IAnswer<SynchronousServletOutputStreamAdapter>() {
+ @Override
+ public SynchronousServletOutputStreamAdapter answer() {
+ return new SynchronousServletOutputStreamAdapter() {
+ @Override
+ public void write( int b ) throws IOException {
+ /* do nothing */
+ }
+ };
+ }
+ }).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);
+ } catch (IOException e) {
+ //this is expected after the failover limit is reached
+ }
+ /* sticky session is on do not loadbalance */
+ Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
+ }
+
+
@Test
public void testConnectivityActive() throws Exception {
String serviceName = "OOZIE";