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) {