You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2020/10/23 17:49:26 UTC

[knox] branch master updated: KNOX-843 - Client side HA (#380)

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 673bb4b  KNOX-843 - Client side HA (#380)
673bb4b is described below

commit 673bb4b9532fba3f4ad9b6238340b787109f723c
Author: Sandeep Moré <mo...@gmail.com>
AuthorDate: Fri Oct 23 13:49:17 2020 -0400

    KNOX-843 - Client side HA (#380)
    
    * KNOX-843 - Client side HA
    
    * KNOX-843 - Added enableLoadBalancing config, updated variables names to enableStickySession and stickySessionCookieName
---
 gateway-provider-ha/pom.xml                        |   6 +-
 .../gateway/ha/dispatch/DefaultHaDispatch.java     | 170 ++++++++++++++++++-
 .../ha/dispatch/i18n/HaDispatchMessages.java       |   3 +
 .../knox/gateway/ha/provider/HaProvider.java       |  15 ++
 .../knox/gateway/ha/provider/HaServiceConfig.java  |  16 ++
 .../knox/gateway/ha/provider/URLManager.java       |   2 +
 .../ha/provider/impl/BaseZookeeperURLManager.java  |   9 +-
 .../ha/provider/impl/DefaultHaProvider.java        |  25 ++-
 .../ha/provider/impl/DefaultHaServiceConfig.java   |  48 ++++++
 .../ha/provider/impl/DefaultURLManager.java        |   9 +-
 .../ha/provider/impl/HaDescriptorConstants.java    |   8 +
 .../ha/provider/impl/HaDescriptorFactory.java      |  31 +++-
 .../ha/provider/impl/HaDescriptorManager.java      |  12 +-
 .../ha/provider/impl/HaServiceConfigConstants.java |  16 ++
 .../gateway/ha/dispatch/DefaultHaDispatchTest.java | 183 ++++++++++++++++++++-
 .../ha/provider/impl/HaDescriptorFactoryTest.java  |  62 ++++++-
 .../ha/provider/impl/HaDescriptorManagerTest.java  |  50 +++++-
 .../knox/gateway/rm/dispatch/RMHaDispatchTest.java |   4 +-
 .../hdfs/dispatch/WebHdfsHaDispatchTest.java       |   2 +-
 .../knox/gateway/dispatch/DefaultDispatch.java     |  41 ++++-
 20 files changed, 674 insertions(+), 38 deletions(-)

diff --git a/gateway-provider-ha/pom.xml b/gateway-provider-ha/pom.xml
index ea5da54..4fedd20 100644
--- a/gateway-provider-ha/pom.xml
+++ b/gateway-provider-ha/pom.xml
@@ -49,7 +49,11 @@
             <groupId>org.apache.knox</groupId>
             <artifactId>gateway-util-configinjector</artifactId>
         </dependency>
-        
+
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+        </dependency>
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/DefaultHaDispatch.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/DefaultHaDispatch.java
index b18c25d..0e479b1 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/DefaultHaDispatch.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/dispatch/DefaultHaDispatch.java
@@ -17,7 +17,13 @@
  */
 package org.apache.knox.gateway.ha.dispatch;
 
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.utils.URIBuilder;
 import org.apache.knox.gateway.config.Configure;
+import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.dispatch.DefaultDispatch;
 import org.apache.knox.gateway.filter.AbstractGatewayFilter;
 import org.apache.knox.gateway.ha.dispatch.i18n.HaDispatchMessages;
@@ -25,15 +31,23 @@ 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.i18n.messages.MessagesFactory;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.client.methods.HttpUriRequest;
 
+import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 /**
  * Default HA dispatch class that has a very basic failover mechanism
@@ -44,10 +58,17 @@ public class DefaultHaDispatch extends DefaultDispatch {
 
   protected static final HaDispatchMessages LOG = MessagesFactory.get(HaDispatchMessages.class);
 
-  private int maxFailoverAttempts = HaServiceConfigConstants.DEFAULT_MAX_FAILOVER_ATTEMPTS;
+  private static final Map<String, String> urlToHashLookup = new HashMap<>();
+  private static final Map<String, String> hashToUrlLookup = new HashMap<>();
 
+  private int maxFailoverAttempts = HaServiceConfigConstants.DEFAULT_MAX_FAILOVER_ATTEMPTS;
   private int failoverSleep = HaServiceConfigConstants.DEFAULT_FAILOVER_SLEEP;
 
+  private boolean stickySessionsEnabled = HaServiceConfigConstants.DEFAULT_STICKY_SESSIONS_ENABLED;
+  private boolean loadBalancingEnabled = HaServiceConfigConstants.DEFAULT_LOAD_BALANCING_ENABLED;
+  private boolean noFallbackEnabled = HaServiceConfigConstants.DEFAULT_NO_FALLBACK_ENABLED;
+  private String stickySessionCookieName = HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME;
+
   private HaProvider haProvider;
 
   @Override
@@ -58,6 +79,23 @@ public class DefaultHaDispatch extends DefaultDispatch {
       HaServiceConfig serviceConfig = haProvider.getHaDescriptor().getServiceConfig(getServiceRole());
       maxFailoverAttempts = serviceConfig.getMaxFailoverAttempts();
       failoverSleep = serviceConfig.getFailoverSleep();
+      stickySessionsEnabled = serviceConfig.isStickySessionEnabled();
+      loadBalancingEnabled = serviceConfig.isLoadBalancingEnabled();
+      noFallbackEnabled = serviceConfig.isNoFallbackEnabled();
+      stickySessionCookieName = serviceConfig.getStickySessionCookieName();
+      setupUrlHashLookup();
+    }
+
+    // Suffix the cookie name by the service to make it unique
+    // The cookie path is NOT unique since Knox is stripping the service name.
+    stickySessionCookieName = stickySessionCookieName + '-' + getServiceRole();
+  }
+
+  private void setupUrlHashLookup() {
+    for (String url : haProvider.getURLs(getServiceRole())) {
+      String urlHash = hash(url);
+      urlToHashLookup.put(url, urlHash);
+      hashToUrlLookup.put(urlHash, url);
     }
   }
 
@@ -71,6 +109,30 @@ public class DefaultHaDispatch extends DefaultDispatch {
   }
 
   @Override
+  protected void executeRequestWrapper(HttpUriRequest outboundRequest,
+      HttpServletRequest inboundRequest, HttpServletResponse outboundResponse)
+      throws IOException {
+    final Optional<URI> opt = setBackendfromHaCookie(outboundRequest, inboundRequest);
+    if(opt.isPresent()) {
+      ((HttpRequestBase) outboundRequest).setURI(opt.get());
+    }
+    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)
+     */
+    if ( (!opt.isPresent() && stickySessionsEnabled) || loadBalancingEnabled) {
+      haProvider.makeNextActiveURLAvailable(getServiceRole());
+    }
+  }
+
+  @Override
+  protected void outboundResponseWrapper(final HttpServletRequest inboundRequest, HttpServletResponse outboundResponse) {
+    setKnoxHaCookie(inboundRequest, outboundResponse);
+  }
+
+  @Override
   protected void executeRequest(HttpUriRequest outboundRequest, HttpServletRequest inboundRequest, HttpServletResponse outboundResponse) throws IOException {
     HttpResponse inboundResponse = null;
     try {
@@ -82,8 +144,71 @@ public class DefaultHaDispatch extends DefaultDispatch {
     }
   }
 
+  private Optional<URI> setBackendfromHaCookie(HttpUriRequest outboundRequest, HttpServletRequest inboundRequest) {
+    if (stickySessionsEnabled && inboundRequest.getCookies() != null) {
+      for (Cookie cookie : inboundRequest.getCookies()) {
+        if (stickySessionCookieName.equals(cookie.getName())) {
+          String backendURLHash = cookie.getValue();
+          String backendURL = hashToUrlLookup.get(backendURLHash);
+          // Make sure that the url provided is actually a valid backend url
+          if (haProvider.getURLs(getServiceRole()).contains(backendURL)) {
+            try {
+              URI cookieUri = new URI(backendURL);
+              URIBuilder uriBuilder = new URIBuilder(outboundRequest.getURI());
+              uriBuilder.setScheme(cookieUri.getScheme());
+              uriBuilder.setHost(cookieUri.getHost());
+              uriBuilder.setPort(cookieUri.getPort());
+              URI uri = uriBuilder.build();
+              return Optional.of(uri);
+            } catch (URISyntaxException ignore) {
+              // The cookie was invalid so we just don't set it. Knox will pick a backend automatically
+            }
+          }
+        }
+      }
+    }
+    return Optional.empty();
+  }
+
+  private void setKnoxHaCookie(HttpServletRequest inboundRequest,
+      HttpServletResponse outboundResponse) {
+    if (stickySessionsEnabled) {
+      List<Cookie> serviceHaCookies = Collections.emptyList();
+      if(inboundRequest.getCookies() != null) {
+        serviceHaCookies = Arrays
+            .stream(inboundRequest.getCookies())
+            .filter(cookie -> stickySessionCookieName.equals(cookie.getName()))
+            .collect(Collectors.toList());
+      }
+      /* if the inbound request has a valid hash then no need to set a different hash */
+      if (serviceHaCookies != null && !serviceHaCookies.isEmpty()
+          && hashToUrlLookup.containsKey(serviceHaCookies.get(0).getValue())) {
+        return;
+      } else {
+        String url = haProvider.getActiveURL(getServiceRole());
+        String cookieValue = urlToHashLookup.get(url);
+        Cookie stickySessionCookie = new Cookie(stickySessionCookieName, cookieValue);
+        stickySessionCookie.setPath(inboundRequest.getContextPath());
+        stickySessionCookie.setMaxAge(-1);
+        stickySessionCookie.setHttpOnly(true);
+        GatewayConfig config = (GatewayConfig) inboundRequest
+            .getServletContext()
+            .getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
+        if (config != null) {
+          stickySessionCookie.setSecure(config.isSSLEnabled());
+        }
+        outboundResponse.addCookie(stickySessionCookie);
+      }
+    }
+  }
 
   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) {
+      LOG.noFallbackError();
+      outboundResponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, "Service connection error, HA failover disabled");
+      return;
+    }
     LOG.failingOverRequest(outboundRequest.getURI().toString());
     AtomicInteger counter = (AtomicInteger) inboundRequest.getAttribute(FAILOVER_COUNTER_ATTRIBUTE);
     if ( counter == null ) {
@@ -92,8 +217,11 @@ public class DefaultHaDispatch extends DefaultDispatch {
     inboundRequest.setAttribute(FAILOVER_COUNTER_ATTRIBUTE, counter);
     if ( counter.incrementAndGet() <= maxFailoverAttempts ) {
       haProvider.markFailedURL(getServiceRole(), outboundRequest.getURI().toString());
+      setupUrlHashLookup(); // refresh the url hash after failing a url
       //null out target url so that rewriters run again
       inboundRequest.setAttribute(AbstractGatewayFilter.TARGET_REQUEST_URL_ATTRIBUTE_NAME, null);
+      // Make sure to remove the cookie ha cookie from the request
+      inboundRequest = new StickySessionCookieRemovedRequest(stickySessionCookieName, inboundRequest);
       URI uri = getDispatchUrl(inboundRequest);
       ((HttpRequestBase) outboundRequest).setURI(uri);
       if ( failoverSleep > 0 ) {
@@ -114,4 +242,38 @@ public class DefaultHaDispatch extends DefaultDispatch {
       }
     }
   }
+
+  private String hash(String url) {
+    return DigestUtils.sha256Hex(url);
+  }
+
+  /**
+   * Strips out the cookies by the cookie name provided
+   */
+  private static class StickySessionCookieRemovedRequest extends HttpServletRequestWrapper {
+    private final Cookie[] cookies;
+
+    StickySessionCookieRemovedRequest(String cookieName, HttpServletRequest request) {
+      super(request);
+      this.cookies = filterCookies(cookieName, request.getCookies());
+    }
+
+    private Cookie[] filterCookies(String cookieName, Cookie[] cookies) {
+      if (super.getCookies() == null) {
+        return null;
+      }
+      List<Cookie> cookiesInternal = new ArrayList<>();
+      for (Cookie cookie : cookies) {
+        if (!cookieName.equals(cookie.getName())) {
+          cookiesInternal.add(cookie);
+        }
+      }
+      return cookiesInternal.toArray(new Cookie[0]);
+    }
+
+    @Override
+    public Cookie[] getCookies() {
+      return cookies;
+    }
+  }
 }
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 d597c47..9f72638 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
@@ -41,4 +41,7 @@ public interface HaDispatchMessages {
 
   @Message(level = MessageLevel.INFO, text = "Error occurred while trying to sleep for failover : {0} {1}")
   void failoverSleepFailed(String service, @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+  @Message(level = MessageLevel.ERROR, text = "noFallback flag is turned on for sticky session so aborting request without retrying")
+  void noFallbackError();
 }
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/HaProvider.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/HaProvider.java
index 6df79cb..ff543f6 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/HaProvider.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/HaProvider.java
@@ -64,4 +64,19 @@ public interface HaProvider {
    */
   void markFailedURL(String serviceName, String url);
 
+  /**
+   * This method puts changes the active URL to
+   * the next available URL for the service.
+   *
+   * @param serviceName the name of the service
+   */
+  void makeNextActiveURLAvailable(String serviceName);
+
+  /**
+   * This method puts gets all the currently
+   * available URLs for the service.
+   *
+   * @param serviceName the name of the service
+   */
+  List<String> getURLs(String serviceName);
 }
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 dacb8c6..f0582b4 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
@@ -41,4 +41,20 @@ public interface HaServiceConfig {
   String getZookeeperNamespace();
 
   void setZookeeperNamespace(String zookeeperNamespace);
+
+  boolean isLoadBalancingEnabled();
+
+  void setLoadBalancingEnabled(boolean isLoadBalancingEnabled);
+
+  boolean isStickySessionEnabled();
+
+  void setStickySessionEnabled(boolean stickySessionEnabled);
+
+  String getStickySessionCookieName();
+
+  void setStickySessionCookieName(String stickySessionCookieName);
+
+  boolean isNoFallbackEnabled();
+
+  void setNoFallbackEnabled(boolean noFallbackEnabled);
 }
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/URLManager.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/URLManager.java
index 7f54c56..383cd8d 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/URLManager.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/URLManager.java
@@ -33,4 +33,6 @@ public interface URLManager {
   void setURLs(List<String> urls);
 
   void markFailed(String url);
+
+  void makeNextActiveURLAvailable();
 }
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/BaseZookeeperURLManager.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/BaseZookeeperURLManager.java
index 65f42ec..d3e61ab 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/BaseZookeeperURLManager.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/BaseZookeeperURLManager.java
@@ -57,9 +57,10 @@ public abstract class BaseZookeeperURLManager implements URLManager {
    */
   private static final int TIMEOUT = 5000;
 
+  private final ConcurrentLinkedQueue<String> urls = new ConcurrentLinkedQueue<>();
+
   private String zooKeeperEnsemble;
   private String zooKeeperNamespace;
-  private ConcurrentLinkedQueue<String> urls = new ConcurrentLinkedQueue<>();
 
   // -------------------------------------------------------------------------------------
   // URLManager interface methods
@@ -115,6 +116,12 @@ public abstract class BaseZookeeperURLManager implements URLManager {
   }
 
   @Override
+  public void makeNextActiveURLAvailable() {
+    String head = urls.poll();
+    urls.offer(head);
+  }
+
+  @Override
   public synchronized void setURLs(List<String> urls) {
     if ((urls != null) && (!(urls.isEmpty()))) {
       this.urls.clear();
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultHaProvider.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultHaProvider.java
index adc3606..8477e83 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultHaProvider.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultHaProvider.java
@@ -25,6 +25,7 @@ import org.apache.knox.gateway.ha.provider.URLManagerLoader;
 import org.apache.knox.gateway.ha.provider.impl.i18n.HaMessages;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -60,10 +61,7 @@ public class DefaultHaProvider implements HaProvider {
   @Override
   public boolean isHaEnabled(String serviceName) {
     HaServiceConfig config = descriptor.getServiceConfig(serviceName);
-    if ( config != null && config.isEnabled() ) {
-      return true;
-    }
-    return false;
+    return config != null && config.isEnabled();
   }
 
   @Override
@@ -93,4 +91,23 @@ public class DefaultHaProvider implements HaProvider {
       LOG.noServiceFound(serviceName);
     }
   }
+
+  @Override
+  public void makeNextActiveURLAvailable(String serviceName) {
+    if ( haServices.containsKey(serviceName) ) {
+      haServices.get(serviceName).makeNextActiveURLAvailable();
+    } else {
+      LOG.noServiceFound(serviceName);
+    }
+  }
+
+  @Override
+  public List<String> getURLs(String serviceName) {
+    if ( haServices.containsKey(serviceName) ) {
+      return haServices.get(serviceName).getURLs();
+    } else {
+      LOG.noServiceFound(serviceName);
+      return Collections.emptyList();
+    }
+  }
 }
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 2bf6bf3..3a8a654 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
@@ -29,6 +29,14 @@ public class DefaultHaServiceConfig implements HaServiceConfig, HaServiceConfigC
 
   private int failoverSleep = DEFAULT_FAILOVER_SLEEP;
 
+  private boolean isStickySessionEnabled = DEFAULT_STICKY_SESSIONS_ENABLED;
+
+  private boolean isLoadBalancingEnabled = DEFAULT_LOAD_BALANCING_ENABLED;
+
+  private boolean isNoFallbackEnabled = DEFAULT_NO_FALLBACK_ENABLED;
+
+  private String stickySessionCookieName = DEFAULT_STICKY_SESSION_COOKIE_NAME;
+
   private String zookeeperEnsemble;
 
   private String zookeeperNamespace;
@@ -96,4 +104,44 @@ public class DefaultHaServiceConfig implements HaServiceConfig, HaServiceConfigC
   public void setZookeeperNamespace(String zookeeperNamespace) {
     this.zookeeperNamespace = zookeeperNamespace;
   }
+
+  @Override
+  public boolean isStickySessionEnabled() {
+    return  this.isStickySessionEnabled;
+  }
+
+  @Override
+  public void setStickySessionEnabled(boolean stickySessionEnabled) {
+    this.isStickySessionEnabled = stickySessionEnabled;
+  }
+
+  @Override
+  public String getStickySessionCookieName() {
+    return this.stickySessionCookieName;
+  }
+
+  @Override
+  public void setStickySessionCookieName(String stickySessionCookieName) {
+    this.stickySessionCookieName = stickySessionCookieName;
+  }
+
+  @Override
+  public boolean isLoadBalancingEnabled() {
+    return this.isLoadBalancingEnabled;
+  }
+
+  @Override
+  public void setLoadBalancingEnabled(boolean isLoadBalancingEnabled) {
+    this.isLoadBalancingEnabled = isLoadBalancingEnabled;
+  }
+
+  @Override
+  public boolean isNoFallbackEnabled() {
+    return isNoFallbackEnabled;
+  }
+
+  @Override
+  public void setNoFallbackEnabled(boolean noFallbackEnabled) {
+    isNoFallbackEnabled = noFallbackEnabled;
+  }
 }
diff --git a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultURLManager.java b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultURLManager.java
index 6f909c2..34b2cc8 100644
--- a/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultURLManager.java
+++ b/gateway-provider-ha/src/main/java/org/apache/knox/gateway/ha/provider/impl/DefaultURLManager.java
@@ -31,8 +31,7 @@ public class DefaultURLManager implements URLManager {
 
   private static final HaMessages LOG = MessagesFactory.get(HaMessages.class);
 
-  private ConcurrentLinkedQueue<String> urls = new ConcurrentLinkedQueue<>();
-
+  private final ConcurrentLinkedQueue<String> urls = new ConcurrentLinkedQueue<>();
 
   @Override
   public boolean supportsConfig(HaServiceConfig config) {
@@ -97,4 +96,10 @@ public class DefaultURLManager implements URLManager {
       }
     }
   }
+
+  @Override
+  public synchronized void makeNextActiveURLAvailable() {
+    String head = urls.poll();
+    urls.offer(head);
+  }
 }
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 a23f732..5025863 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
@@ -43,4 +43,12 @@ public interface HaDescriptorConstants {
    String ZOOKEEPER_ENSEMBLE = "zookeeperEnsemble";
 
    String ZOOKEEPER_NAMESPACE = "zookeeperNamespace";
+
+   String ENABLE_LOAD_BALANCING = "enableLoadBalancing";
+
+   String ENABLE_STICKY_SESSIONS = "enableStickySession";
+
+   String ENABLE_NO_FALLBACK = "noFallback";
+
+   String STICKY_SESSION_COOKIE_NAME = "stickySessionCookieName";
 }
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 c8ff28e..411a3d6 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
@@ -36,16 +36,27 @@ public abstract class HaDescriptorFactory implements HaServiceConfigConstants {
       String failoverSleep = configMap.get(CONFIG_PARAM_FAILOVER_SLEEP);
       String zookeeperEnsemble = configMap.get(CONFIG_PARAM_ZOOKEEPER_ENSEMBLE);
       String zookeeperNamespace = configMap.get(CONFIG_PARAM_ZOOKEEPER_NAMESPACE);
+      String stickySessionEnabled = configMap.get(CONFIG_STICKY_SESSIONS_ENABLED);
+      String loadBalancingEnabled = configMap.get(CONFIG_LOAD_BALANCING_ENABLED);
+      String stickySessionCookieName = configMap.get(STICKY_SESSION_COOKIE_NAME);
+      String noFallbackEnabled = configMap.get(CONFIG_NO_FALLBACK_ENABLED);
       return createServiceConfig(serviceName, enabledValue, maxFailoverAttempts, failoverSleep,
-          zookeeperEnsemble, zookeeperNamespace);
+          zookeeperEnsemble, zookeeperNamespace, loadBalancingEnabled, stickySessionEnabled, stickySessionCookieName, noFallbackEnabled);
    }
 
    public static HaServiceConfig createServiceConfig(String serviceName, String enabledValue,
                                                      String maxFailoverAttemptsValue, String failoverSleepValue,
-                                                     String zookeeperEnsemble, String zookeeperNamespace) {
+                                                     String zookeeperEnsemble, String zookeeperNamespace,
+                                                     String loadBalancingEnabledValue, String stickySessionsEnabledValue,
+                                                     String stickySessionCookieNameValue,
+                                                     String noFallbackEnabledValue) {
       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;
       if (enabledValue != null && !enabledValue.trim().isEmpty()) {
          enabled = Boolean.parseBoolean(enabledValue);
       }
@@ -55,6 +66,18 @@ public abstract class HaDescriptorFactory implements HaServiceConfigConstants {
       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);
+      }
 
       DefaultHaServiceConfig serviceConfig = new DefaultHaServiceConfig(serviceName);
       serviceConfig.setEnabled(enabled);
@@ -62,6 +85,10 @@ public abstract class HaDescriptorFactory implements HaServiceConfigConstants {
       serviceConfig.setFailoverSleep(failoverSleep);
       serviceConfig.setZookeeperEnsemble(zookeeperEnsemble);
       serviceConfig.setZookeeperNamespace(zookeeperNamespace);
+      serviceConfig.setStickySessionEnabled(stickySessionsEnabled);
+      serviceConfig.setLoadBalancingEnabled(loadBalancingEnabled);
+      serviceConfig.setStickySessionCookieName(stickySessionCookieName);
+      serviceConfig.setNoFallbackEnabled(noFallbackEnabled);
       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 bf52a48..4b4487d 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
@@ -60,6 +60,12 @@ public class HaDescriptorManager implements HaDescriptorConstants {
                if (config.getZookeeperNamespace() != null) {
                  serviceElement.setAttribute(ZOOKEEPER_NAMESPACE, config.getZookeeperNamespace());
                }
+               serviceElement.setAttribute(ENABLE_LOAD_BALANCING, Boolean.toString(config.isLoadBalancingEnabled()));
+               serviceElement.setAttribute(ENABLE_STICKY_SESSIONS, Boolean.toString(config.isStickySessionEnabled()));
+               serviceElement.setAttribute(ENABLE_NO_FALLBACK, Boolean.toString(config.isNoFallbackEnabled()));
+               if (config.getStickySessionCookieName() != null) {
+                 serviceElement.setAttribute(STICKY_SESSION_COOKIE_NAME, config.getStickySessionCookieName());
+               }
                root.appendChild(serviceElement);
             }
          }
@@ -85,7 +91,11 @@ public class HaDescriptorManager implements HaDescriptorConstants {
                      element.getAttribute(MAX_FAILOVER_ATTEMPTS),
                      element.getAttribute(FAILOVER_SLEEP),
                      element.getAttribute(ZOOKEEPER_ENSEMBLE),
-                     element.getAttribute(ZOOKEEPER_NAMESPACE));
+                     element.getAttribute(ZOOKEEPER_NAMESPACE),
+                     element.getAttribute(ENABLE_LOAD_BALANCING),
+                     element.getAttribute(ENABLE_STICKY_SESSIONS),
+                     element.getAttribute(STICKY_SESSION_COOKIE_NAME),
+                     element.getAttribute(ENABLE_NO_FALLBACK));
                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 a8c14fb..52d181c 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
@@ -32,9 +32,25 @@ public interface HaServiceConfigConstants {
 
    String CONFIG_PARAM_ZOOKEEPER_NAMESPACE = "zookeeperNamespace";
 
+   String CONFIG_STICKY_SESSIONS_ENABLED = "enableStickySession";
+
+   String CONFIG_LOAD_BALANCING_ENABLED = "enableLoadBalancing";
+
+   String CONFIG_NO_FALLBACK_ENABLED = "noFallback";
+
+   String STICKY_SESSION_COOKIE_NAME = "stickySessionCookieName";
+
    int DEFAULT_MAX_FAILOVER_ATTEMPTS = 3;
 
    int DEFAULT_FAILOVER_SLEEP = 1000;
 
    boolean DEFAULT_ENABLED = true;
+
+   boolean DEFAULT_STICKY_SESSIONS_ENABLED = false;
+
+   boolean DEFAULT_LOAD_BALANCING_ENABLED = false;
+
+   boolean DEFAULT_NO_FALLBACK_ENABLED = false;
+
+   String DEFAULT_STICKY_SESSION_COOKIE_NAME = "KNOX_BACKEND";
 }
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 d0b5492..dc9bcf2 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,6 +17,7 @@
  */
 package org.apache.knox.gateway.ha.dispatch;
 
+import org.apache.http.impl.client.HttpClients;
 import org.apache.knox.gateway.ha.provider.HaDescriptor;
 import org.apache.knox.gateway.ha.provider.HaProvider;
 import org.apache.knox.gateway.ha.provider.HaServletContextListener;
@@ -48,7 +49,7 @@ public class DefaultHaDispatchTest {
   public void testConnectivityFailover() throws Exception {
     String serviceName = "OOZIE";
     HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
-    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null));
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null, null, null, null, null));
     HaProvider provider = new DefaultHaProvider(descriptor);
     URI uri1 = new URI( "http://unreachable-host.invalid" );
     URI uri2 = new URI( "http://reachable-host.invalid" );
@@ -106,4 +107,184 @@ public class DefaultHaDispatchTest {
     //test to make sure the sleep took place
     Assert.assertTrue(elapsedTime > 1000);
   }
+
+  @Test
+  public void testStickyFailoverNoFallback() throws Exception {
+    String serviceName = "OOZIE";
+    HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null, null, "true", null, "true"));
+    HaProvider provider = new DefaultHaProvider(descriptor);
+    URI uri1 = new URI( "http://unreachable-host.invalid" );
+    URI uri2 = new URI( "http://reachable-host.invalid" );
+    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();
+
+    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();
+
+    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 {
+            throw new IOException( "unreachable-host.invalid" );
+          }
+        };
+      }
+    }).once();
+    EasyMock.replay(filterConfig, servletContext, outboundRequest, inboundRequest, outboundResponse);
+    Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
+    DefaultHaDispatch dispatch = new DefaultHaDispatch();
+    HttpClientBuilder builder = HttpClientBuilder.create();
+    CloseableHttpClient client = builder.build();
+    dispatch.setHttpClient(client);
+    dispatch.setHaProvider(provider);
+    dispatch.setServiceRole(serviceName);
+    dispatch.init();
+    try {
+      dispatch.executeRequest(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));
+  }
+
+  /**
+   * This is a negative test for noFallback flag
+   * When sticky session is disabled noFallback should not have any effect
+   * i.e. request should failover.
+   * @throws Exception
+   */
+  @Test
+  public void testNoFallbackWhenStickyDisabled() throws Exception {
+    String serviceName = "OOZIE";
+    HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null, null, null, null, "true"));
+    HaProvider provider = new DefaultHaProvider(descriptor);
+    URI uri1 = new URI( "http://unreachable-host.invalid" );
+    URI uri2 = new URI( "http://reachable-host.invalid" );
+    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();
+
+    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();
+
+    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 {
+            throw new IOException( "unreachable-host.invalid" );
+          }
+        };
+      }
+    }).once();
+    EasyMock.replay(filterConfig, servletContext, outboundRequest, inboundRequest, outboundResponse);
+    Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
+    DefaultHaDispatch dispatch = new DefaultHaDispatch();
+    HttpClientBuilder builder = HttpClientBuilder.create();
+    CloseableHttpClient client = builder.build();
+    dispatch.setHttpClient(client);
+    dispatch.setHaProvider(provider);
+    dispatch.setServiceRole(serviceName);
+    dispatch.init();
+    long startTime = System.currentTimeMillis();
+    try {
+      dispatch.executeRequest(outboundRequest, inboundRequest, outboundResponse);
+    } catch (IOException e) {
+      //this is expected after the failover limit is reached
+    }
+    long elapsedTime = System.currentTimeMillis() - startTime;
+    Assert.assertEquals(uri2.toString(), provider.getActiveURL(serviceName));
+    //test to make sure the sleep took place
+    Assert.assertTrue(elapsedTime > 1000);
+  }
+
+  @Test
+  public void testConnectivityActive() 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://unreachable-host" );
+    URI uri2 = new URI( "http://reachable-host" );
+    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();
+
+    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();
+
+    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 {
+            throw new IOException( "unreachable-host" );
+          }
+        };
+      }
+    }).once();
+    EasyMock.replay(filterConfig, servletContext, outboundRequest, inboundRequest, outboundResponse);
+    Assert.assertEquals(uri1.toString(), provider.getActiveURL(serviceName));
+    DefaultHaDispatch dispatch = new DefaultHaDispatch();
+    dispatch.setHttpClient(HttpClients.createDefault());
+    dispatch.setHaProvider(provider);
+    dispatch.setServiceRole(serviceName);
+    dispatch.init();
+  }
 }
diff --git a/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorFactoryTest.java b/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorFactoryTest.java
index ea2f62c..6a95711 100644
--- a/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorFactoryTest.java
+++ b/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorFactoryTest.java
@@ -41,12 +41,70 @@ public class HaDescriptorFactoryTest {
       assertEquals(42, serviceConfig.getMaxFailoverAttempts());
       assertEquals(50, serviceConfig.getFailoverSleep());
 
-      serviceConfig = HaDescriptorFactory.createServiceConfig("bar", "false", "3", "1000", null, null);
+      serviceConfig = HaDescriptorFactory.createServiceConfig("bar", "false", "3", "1000", null, null, null, null, null, null);
       assertNotNull(serviceConfig);
       assertFalse(serviceConfig.isEnabled());
       assertEquals("bar", serviceConfig.getServiceName());
       assertEquals(3, serviceConfig.getMaxFailoverAttempts());
       assertEquals(1000, serviceConfig.getFailoverSleep());
-
    }
+
+  @Test
+  public void testCreateServiceConfigActive() {
+    HaServiceConfig serviceConfig = HaDescriptorFactory.createServiceConfig("foo", "enableStickySession=true;enabled=true;maxFailoverAttempts=42;failoverSleep=50;maxRetryAttempts=1;retrySleep=1000");
+    assertNotNull(serviceConfig);
+    assertTrue(serviceConfig.isEnabled());
+    assertEquals("foo", serviceConfig.getServiceName());
+    assertEquals(42, serviceConfig.getMaxFailoverAttempts());
+    assertEquals(50, serviceConfig.getFailoverSleep());
+    assertTrue(serviceConfig.isStickySessionEnabled());
+    assertEquals(HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME, serviceConfig.getStickySessionCookieName());
+
+    serviceConfig = HaDescriptorFactory.createServiceConfig("foo", "enableStickySession=true;enabled=true;maxFailoverAttempts=42;failoverSleep=50;maxRetryAttempts=1;retrySleep=1000;stickySessionCookieName=abc");
+    assertNotNull(serviceConfig);
+    assertTrue(serviceConfig.isEnabled());
+    assertEquals("foo", serviceConfig.getServiceName());
+    assertEquals(42, serviceConfig.getMaxFailoverAttempts());
+    assertEquals(50, serviceConfig.getFailoverSleep());
+    assertTrue(serviceConfig.isStickySessionEnabled());
+    assertEquals("abc", serviceConfig.getStickySessionCookieName());
+
+    serviceConfig = HaDescriptorFactory.createServiceConfig( "bar", "false", "3", "1000", null, null, null, "true", null, null);
+    assertNotNull(serviceConfig);
+    assertFalse(serviceConfig.isEnabled());
+    assertEquals("bar", serviceConfig.getServiceName());
+    assertEquals(3, serviceConfig.getMaxFailoverAttempts());
+    assertEquals(1000, serviceConfig.getFailoverSleep());
+    assertTrue(serviceConfig.isStickySessionEnabled());
+    assertEquals(HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME, serviceConfig.getStickySessionCookieName());
+
+    serviceConfig = HaDescriptorFactory.createServiceConfig( "knox", "false", "4", "3000", null, null, null, null, null, null);
+    assertNotNull(serviceConfig);
+    assertFalse(serviceConfig.isEnabled());
+    assertEquals("knox", serviceConfig.getServiceName());
+    assertEquals(4, serviceConfig.getMaxFailoverAttempts());
+    assertEquals(3000, serviceConfig.getFailoverSleep());
+    assertFalse(serviceConfig.isStickySessionEnabled());
+    assertEquals(HaServiceConfigConstants.DEFAULT_STICKY_SESSION_COOKIE_NAME, serviceConfig.getStickySessionCookieName());
+
+    serviceConfig = HaDescriptorFactory.createServiceConfig( "bar", "false", "3", "1000", null, null, null, "true", "abc", null);
+    assertNotNull(serviceConfig);
+    assertFalse(serviceConfig.isEnabled());
+    assertEquals("bar", serviceConfig.getServiceName());
+    assertEquals(3, serviceConfig.getMaxFailoverAttempts());
+    assertEquals(1000, serviceConfig.getFailoverSleep());
+    assertTrue(serviceConfig.isStickySessionEnabled());
+    assertEquals("abc", serviceConfig.getStickySessionCookieName());
+
+    serviceConfig = HaDescriptorFactory.createServiceConfig( "bar", "false", "3", "1000", null, null, "true", null, "abc", "true");
+    assertNotNull(serviceConfig);
+    assertFalse(serviceConfig.isEnabled());
+    assertEquals("bar", serviceConfig.getServiceName());
+    assertEquals(3, serviceConfig.getMaxFailoverAttempts());
+    assertEquals(1000, serviceConfig.getFailoverSleep());
+    assertFalse(serviceConfig.isStickySessionEnabled());
+    assertTrue(serviceConfig.isLoadBalancingEnabled());
+    assertTrue(serviceConfig.isNoFallbackEnabled());
+    assertEquals("abc", serviceConfig.getStickySessionCookieName());
+  }
 }
diff --git a/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorManagerTest.java b/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorManagerTest.java
index 5aaf3c7..5f5e36c 100644
--- a/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorManagerTest.java
+++ b/gateway-provider-ha/src/test/java/org/apache/knox/gateway/ha/provider/impl/HaDescriptorManagerTest.java
@@ -49,6 +49,8 @@ public class HaDescriptorManagerTest {
       assertEquals(42, config.getMaxFailoverAttempts());
       assertEquals(4000, config.getFailoverSleep());
       assertFalse(config.isEnabled());
+      assertFalse(config.isStickySessionEnabled());
+      assertFalse(config.isNoFallbackEnabled());
       config = descriptor.getServiceConfig("bar");
       assertTrue(config.isEnabled());
    }
@@ -66,21 +68,51 @@ public class HaDescriptorManagerTest {
       assertEquals(HaServiceConfigConstants.DEFAULT_MAX_FAILOVER_ATTEMPTS, config.getMaxFailoverAttempts());
       assertEquals(HaServiceConfigConstants.DEFAULT_FAILOVER_SLEEP, config.getFailoverSleep());
       assertEquals(HaServiceConfigConstants.DEFAULT_ENABLED, config.isEnabled());
+      assertEquals(HaServiceConfigConstants.DEFAULT_LOAD_BALANCING_ENABLED, config.isLoadBalancingEnabled());
+      assertEquals(HaServiceConfigConstants.DEFAULT_STICKY_SESSIONS_ENABLED, config.isStickySessionEnabled());
+     assertEquals(HaServiceConfigConstants.DEFAULT_NO_FALLBACK_ENABLED, config.isNoFallbackEnabled());
    }
 
    @Test
    public void testDescriptorStoring() throws IOException {
       HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
-      descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("foo", "false", "42", "1000", "foo:2181,bar:2181", "hiveserver2"));
-      descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("bar", "true", "3", "5000", null, null));
+      descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("foo", "false", "42", "1000", "foo:2181,bar:2181", "hiveserver2", null, null, null, null));
+      descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("bar", "true", "3", "5000", null, null, null, null, null, null));
       StringWriter writer = new StringWriter();
       HaDescriptorManager.store(descriptor, writer);
-      String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" +
-            "<ha>\n" +
-            "  <service enabled=\"false\" failoverSleep=\"1000\" maxFailoverAttempts=\"42\" name=\"foo\" zookeeperEnsemble=\"foo:2181,bar:2181\" zookeeperNamespace=\"hiveserver2\"/>\n" +
-            "  <service enabled=\"true\" failoverSleep=\"5000\" maxFailoverAttempts=\"3\" name=\"bar\"/>\n" +
-            "</ha>\n";
-      assertThat( the( xml ), hasXPath( "/ha/service[@enabled='false' and @failoverSleep='1000' and @maxFailoverAttempts='42' and @name='foo' and @zookeeperEnsemble='foo:2181,bar:2181' and @zookeeperNamespace='hiveserver2']" ) );
-      assertThat( the( xml ), hasXPath( "/ha/service[@enabled='true' and @failoverSleep='5000' and @maxFailoverAttempts='3' and @name='bar']" ) );
+      String xml = writer.toString();
+      assertThat( the( xml ), hasXPath( "/ha//service[@enabled='false' and @failoverSleep='1000' and @maxFailoverAttempts='42' and @name='foo' and @zookeeperEnsemble='foo:2181,bar:2181' and @zookeeperNamespace='hiveserver2']" ) );
+      assertThat( the( xml ), hasXPath( "/ha//service[@enabled='true' and @failoverSleep='5000' and @maxFailoverAttempts='3' and @name='bar']" ) );
    }
+
+  @Test
+  public void testDescriptorStoringStickySessionCookie() throws IOException {
+    HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("foo", "false", "42", "1000", "foo:2181,bar:2181", "hiveserver2", null, "true", null, null));
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("bar", "true", "3", "5000", null, null, null, "true", null, null));
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("abc", "true", "3", "5000", null, null, null, "true", "abc", null));
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("xyz", "true", "3", "5000", null, null, null, "true", "xyz", "true"));
+
+    StringWriter writer = new StringWriter();
+    HaDescriptorManager.store(descriptor, writer);
+    String xml = writer.toString();
+    assertThat( the( xml ), hasXPath( "/ha//service[@enabled='false' and @failoverSleep='1000' and @maxFailoverAttempts='42' and @name='foo' and @zookeeperEnsemble='foo:2181,bar:2181' and @zookeeperNamespace='hiveserver2' and @enableStickySession='true']" ) );
+    assertThat( the( xml ), hasXPath( "/ha//service[@enabled='true' and @failoverSleep='5000' and @maxFailoverAttempts='3' and @name='bar' and @enableStickySession='true']" ) );
+    assertThat( the( xml ), hasXPath( "/ha//service[@enabled='true' and @failoverSleep='5000' and @maxFailoverAttempts='3' and @name='abc' and @enableStickySession='true' and @stickySessionCookieName='abc']" ) );
+    assertThat( the( xml ), hasXPath( "/ha//service[@enabled='true' and @failoverSleep='5000' and @maxFailoverAttempts='3' and @name='xyz' and @enableStickySession='true' and @stickySessionCookieName='xyz' and @noFallback='true']" ) );
+  }
+
+  @Test
+  public void testDescriptorStoringLoadBalancerConfig() throws IOException {
+    HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("foo", "false", "42", "1000", "foo:2181,bar:2181", "hiveserver2", "true", "false", null, null));
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("bar", "true", "3", "5000", null, null, "true", null, null, null));
+    descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig("abc", "true", "3", "5000", null, null, null, "true", "abc", null));
+    StringWriter writer = new StringWriter();
+    HaDescriptorManager.store(descriptor, writer);
+    String xml = writer.toString();
+    assertThat( the( xml ), hasXPath( "/ha//service[@enabled='false' and @failoverSleep='1000' and @maxFailoverAttempts='42' and @name='foo' and @zookeeperEnsemble='foo:2181,bar:2181' and @zookeeperNamespace='hiveserver2' and @enableLoadBalancing='true' and @enableStickySession='false']" ) );
+    assertThat( the( xml ), hasXPath( "/ha//service[@enabled='true' and @failoverSleep='5000' and @maxFailoverAttempts='3' and @name='bar' and @enableLoadBalancing='true' and @enableStickySession='false']" ) );
+    assertThat( the( xml ), hasXPath( "/ha//service[@enabled='true' and @failoverSleep='5000' and @maxFailoverAttempts='3' and @name='abc' and @enableLoadBalancing='false' and @enableStickySession='true' and @stickySessionCookieName='abc']" ) );
+  }
 }
diff --git a/gateway-service-rm/src/test/java/org/apache/knox/gateway/rm/dispatch/RMHaDispatchTest.java b/gateway-service-rm/src/test/java/org/apache/knox/gateway/rm/dispatch/RMHaDispatchTest.java
index 0c3b1b9..55089d8 100644
--- a/gateway-service-rm/src/test/java/org/apache/knox/gateway/rm/dispatch/RMHaDispatchTest.java
+++ b/gateway-service-rm/src/test/java/org/apache/knox/gateway/rm/dispatch/RMHaDispatchTest.java
@@ -58,7 +58,7 @@ public class RMHaDispatchTest {
     public void testConnectivityFailure() throws Exception {
         String serviceName = "RESOURCEMANAGER";
         HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
-        descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null));
+        descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null, null, null, null, null));
         HaProvider provider = new DefaultHaProvider(descriptor);
         URI uri1 = new URI("http://unreachable-host.invalid");
         URI uri2 = new URI("http://reachable-host.invalid");
@@ -129,7 +129,7 @@ public class RMHaDispatchTest {
     public void testConnectivityFailover() throws Exception {
         String serviceName = "RESOURCEMANAGER";
         HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
-        descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null));
+        descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null, null, null, null, null));
         HaProvider provider = new DefaultHaProvider(descriptor);
         URI uri1 = new URI("http://passive-host.invalid");
         URI uri2 = new URI("http://other-host.invalid");
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 02a4114..518a937 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
@@ -48,7 +48,7 @@ public class WebHdfsHaDispatchTest {
    public void testConnectivityFailover() throws Exception {
       String serviceName = "WEBHDFS";
       HaDescriptor descriptor = HaDescriptorFactory.createDescriptor();
-      descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null));
+      descriptor.addServiceConfig(HaDescriptorFactory.createServiceConfig(serviceName, "true", "1", "1000", null, null, null, null, null, null));
       HaProvider provider = new DefaultHaProvider(descriptor);
       URI uri1 = new URI( "http://unreachable-host.invalid" );
       URI uri2 = new URI( "http://reachable-host.invalid" );
diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
index 0d4608c..304d2c6 100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
@@ -24,9 +24,9 @@ import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpHead;
 import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpPatch;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
-import org.apache.http.client.methods.HttpPatch;
 import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.entity.ContentType;
 import org.apache.knox.gateway.SpiGatewayMessages;
@@ -116,6 +116,29 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
     LOG.setReplayBufferSize(replayBufferSize, getServiceRole());
   }
 
+  /**
+   * Wrapper around execute request to accommodate any
+   * request processing such as additional HA logic.
+   * @param outboundRequest
+   * @param inboundRequest
+   * @param outboundResponse
+   * @throws IOException
+   */
+  protected void executeRequestWrapper(HttpUriRequest outboundRequest,
+      HttpServletRequest inboundRequest, HttpServletResponse outboundResponse)
+      throws IOException {
+    executeRequest(outboundRequest, inboundRequest, outboundResponse);
+  }
+
+  /**
+   * A outbound response wrapper used by classes extending this class
+   * to modify any outgoing
+   * response i.e. cookies
+   */
+  protected void outboundResponseWrapper(final HttpServletRequest inboundRequest, HttpServletResponse outboundResponse) {
+    /* no-op */
+  }
+
   protected void executeRequest(
          HttpUriRequest outboundRequest,
          HttpServletRequest inboundRequest,
@@ -159,6 +182,8 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
   }
 
   protected void writeOutboundResponse(HttpUriRequest outboundRequest, HttpServletRequest inboundRequest, HttpServletResponse outboundResponse, HttpResponse inboundResponse) throws IOException {
+    /* in case any changes to outbound response are needed */
+    outboundResponseWrapper(inboundRequest, outboundResponse);
     // Copy the client respond header to the server respond.
     outboundResponse.setStatus(inboundResponse.getStatusLine().getStatusCode());
     copyResponseHeaderFields(outboundResponse, inboundResponse);
@@ -267,14 +292,14 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
       // and setting params here causes configuration setup there to be ignored there.
       // method.getParams().setBooleanParameter("http.protocol.handle-redirects", false);
       copyRequestHeaderFields(method, request);
-      executeRequest(method, request, response);
+     executeRequestWrapper(method, request, response);
    }
 
    @Override
    public void doOptions(URI url, HttpServletRequest request, HttpServletResponse response)
          throws IOException {
       HttpOptions method = new HttpOptions(url);
-      executeRequest(method, request, response);
+     executeRequestWrapper(method, request, response);
    }
 
    @Override
@@ -284,7 +309,7 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
       HttpEntity entity = createRequestEntity(request);
       method.setEntity(entity);
       copyRequestHeaderFields(method, request);
-      executeRequest(method, request, response);
+     executeRequestWrapper(method, request, response);
    }
 
    @Override
@@ -294,7 +319,7 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
       HttpEntity entity = createRequestEntity(request);
       method.setEntity(entity);
       copyRequestHeaderFields(method, request);
-      executeRequest(method, request, response);
+     executeRequestWrapper(method, request, response);
    }
 
    @Override
@@ -304,7 +329,7 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
       HttpEntity entity = createRequestEntity(request);
       method.setEntity(entity);
       copyRequestHeaderFields(method, request);
-      executeRequest(method, request, response);
+     executeRequestWrapper(method, request, response);
    }
 
    @Override
@@ -312,7 +337,7 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
          throws IOException {
       HttpDelete method = new HttpDelete(url);
       copyRequestHeaderFields(method, request);
-      executeRequest(method, request, response);
+     executeRequestWrapper(method, request, response);
    }
 
   @Override
@@ -320,7 +345,7 @@ public class DefaultDispatch extends AbstractGatewayDispatch {
       throws IOException {
     final HttpHead method = new HttpHead(url);
     copyRequestHeaderFields(method, request);
-    executeRequest(method, request, response);
+    executeRequestWrapper(method, request, response);
   }
 
   public void copyResponseHeaderFields(HttpServletResponse outboundResponse, HttpResponse inboundResponse) {