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é <moresand...@gmail.com>
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";

Reply via email to