ソースを参照

YARN-1811. Fixed AMFilters in YARN to correctly accept requests from either web-app proxy or the RMs when HA is enabled. Contributed by Robert Kanter.
svn merge --ignore-ancestry -c 1579877 ../../trunk/


git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-2.4@1579879 13f79535-47bb-0310-9956-ffa450edef68

Vinod Kumar Vavilapalli 11 年 前
コミット
dede57482a

+ 3 - 0
hadoop-yarn-project/CHANGES.txt

@@ -480,6 +480,9 @@ Release 2.4.0 - UNRELEASED
     YARN-1859. Fixed WebAppProxyServlet to correctly handle applications absent
     on the ResourceManager. (Zhijie Shen via vinodkv)
 
+    YARN-1811. Fixed AMFilters in YARN to correctly accept requests from either
+    web-app proxy or the RMs when HA is enabled. (Robert Kanter via vinodkv)
+
 Release 2.3.1 - UNRELEASED
 
   INCOMPATIBLE CHANGES

+ 0 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/HAUtil.java

@@ -264,7 +264,6 @@ public class HAUtil {
   }
 
   /** Add non empty and non null suffix to a key */
-  @VisibleForTesting
   public static String addSuffix(String key, String suffix) {
     if (suffix == null || suffix.isEmpty()) {
       return key;

+ 27 - 2
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/RMHAUtils.java → hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/util/RMHAUtils.java

@@ -16,10 +16,11 @@
  * limitations under the License.
  */
 
-package org.apache.hadoop.yarn.server.resourcemanager;
+package org.apache.hadoop.yarn.util;
 
+import java.util.ArrayList;
 import java.util.Collection;
-
+import java.util.List;
 import org.apache.hadoop.classification.InterfaceAudience.Private;
 import org.apache.hadoop.classification.InterfaceStability.Unstable;
 import org.apache.hadoop.fs.CommonConfigurationKeys;
@@ -67,4 +68,28 @@ public class RMHAUtils {
     HAServiceState haState = proto.getServiceStatus().getState();
     return haState;
   }
+
+  public static List<String> getRMHAWebappAddresses(
+      final YarnConfiguration conf) {
+    Collection<String> rmIds =
+        conf.getStringCollection(YarnConfiguration.RM_HA_IDS);
+    List<String> addrs = new ArrayList<String>();
+    if (YarnConfiguration.useHttps(conf)) {
+      for (String id : rmIds) {
+        String addr = conf.get(
+            YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS + "." + id);
+        if (addr != null) {
+          addrs.add(addr);
+        }
+      }
+    } else {
+      for (String id : rmIds) {
+        String addr = conf.get(YarnConfiguration.RM_WEBAPP_ADDRESS + "." + id);
+        if (addr != null) {
+          addrs.add(addr);
+        }
+      }
+    }
+    return addrs;
+  }
 }

+ 41 - 2
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/util/WebAppUtils.java

@@ -22,6 +22,9 @@ import static org.apache.hadoop.yarn.util.StringHelper.PATH_JOINER;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.hadoop.classification.InterfaceAudience.Private;
 import org.apache.hadoop.classification.InterfaceStability.Evolving;
@@ -30,6 +33,8 @@ import org.apache.hadoop.http.HttpConfig.Policy;
 import org.apache.hadoop.http.HttpServer2;
 import org.apache.hadoop.net.NetUtils;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.conf.HAUtil;
+import org.apache.hadoop.yarn.util.RMHAUtils;
 
 @Private
 @Evolving
@@ -79,6 +84,36 @@ public class WebAppUtils {
           YarnConfiguration.DEFAULT_RM_WEBAPP_ADDRESS);
     }
   }
+
+  public static List<String> getProxyHostsAndPortsForAmFilter(
+      Configuration conf) {
+    List<String> addrs = new ArrayList<String>();
+    String proxyAddr = conf.get(YarnConfiguration.PROXY_ADDRESS);
+    // If PROXY_ADDRESS isn't set, fallback to RM_WEBAPP(_HTTPS)_ADDRESS
+    // There could be multiple if using RM HA
+    if (proxyAddr == null || proxyAddr.isEmpty()) {
+      // If RM HA is enabled, try getting those addresses
+      if (HAUtil.isHAEnabled(conf)) {
+        List<String> haAddrs =
+            RMHAUtils.getRMHAWebappAddresses(new YarnConfiguration(conf));
+        for (String addr : haAddrs) {
+          try {
+            InetSocketAddress socketAddr = NetUtils.createSocketAddr(addr);
+            addrs.add(getResolvedAddress(socketAddr));
+          } catch(IllegalArgumentException e) {
+            // skip if can't resolve
+          }
+        }
+      }
+      // If couldn't resolve any of the addresses or not using RM HA, fallback
+      if (addrs.isEmpty()) {
+        addrs.add(getResolvedRMWebAppURLWithoutScheme(conf));
+      }
+    } else {
+      addrs.add(proxyAddr);
+    }
+    return addrs;
+  }
   
   public static String getProxyHostAndPort(Configuration conf) {
     String addr = conf.get(YarnConfiguration.PROXY_ADDRESS);
@@ -112,10 +147,14 @@ public class WebAppUtils {
               YarnConfiguration.DEFAULT_RM_WEBAPP_ADDRESS,
               YarnConfiguration.DEFAULT_RM_WEBAPP_PORT);      
     }
+    return getResolvedAddress(address);
+  }
+
+  private static String getResolvedAddress(InetSocketAddress address) {
     address = NetUtils.getConnectAddress(address);
-    StringBuffer sb = new StringBuffer();
+    StringBuilder sb = new StringBuilder();
     InetAddress resolved = address.getAddress();
-    if (resolved == null || resolved.isAnyLocalAddress() || 
+    if (resolved == null || resolved.isAnyLocalAddress() ||
         resolved.isLoopbackAddress()) {
       String lh = address.getHostName();
       try {

+ 1 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebApp.java

@@ -25,7 +25,7 @@ import java.net.InetSocketAddress;
 import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.server.resourcemanager.RMContext;
-import org.apache.hadoop.yarn.server.resourcemanager.RMHAUtils;
+import org.apache.hadoop.yarn.util.RMHAUtils;
 import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
 import org.apache.hadoop.yarn.server.resourcemanager.security.QueueACLsManager;
 import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;

+ 18 - 5
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/amfilter/AmFilterInitializer.java

@@ -19,6 +19,7 @@
 package org.apache.hadoop.yarn.server.webproxy.amfilter;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.hadoop.conf.Configuration;
@@ -36,11 +37,23 @@ public class AmFilterInitializer extends FilterInitializer {
   @Override
   public void initFilter(FilterContainer container, Configuration conf) {
     Map<String, String> params = new HashMap<String, String>();
-    String proxy = WebAppUtils.getProxyHostAndPort(conf);
-    String[] parts = proxy.split(":");
-    params.put(AmIpFilter.PROXY_HOST, parts[0]);
-    params.put(AmIpFilter.PROXY_URI_BASE, WebAppUtils.getHttpSchemePrefix(conf)
-        + proxy + getApplicationWebProxyBase());
+    List<String> proxies = WebAppUtils.getProxyHostsAndPortsForAmFilter(conf);
+    StringBuilder sb = new StringBuilder();
+    for (String proxy : proxies) {
+      sb.append(proxy.split(":")[0]).append(AmIpFilter.PROXY_HOSTS_DELIMITER);
+    }
+    sb.setLength(sb.length() - 1);
+    params.put(AmIpFilter.PROXY_HOSTS, sb.toString());
+
+    String prefix = WebAppUtils.getHttpSchemePrefix(conf);
+    String proxyBase = getApplicationWebProxyBase();
+    sb = new StringBuilder();
+    for (String proxy : proxies) {
+      sb.append(prefix).append(proxy).append(proxyBase)
+          .append(AmIpFilter.PROXY_HOSTS_DELIMITER);
+    }
+    sb.setLength(sb.length() - 1);
+    params.put(AmIpFilter.PROXY_URI_BASES, sb.toString());
     container.addFilter(FILTER_NAME, FILTER_CLASS, params);
   }
 

+ 84 - 22
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/main/java/org/apache/hadoop/yarn/server/webproxy/amfilter/AmIpFilter.java

@@ -20,8 +20,12 @@ package org.apache.hadoop.yarn.server.webproxy.amfilter;
 
 import java.io.IOException;
 import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.net.UnknownHostException;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 import javax.servlet.Filter;
@@ -36,42 +40,78 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.classification.InterfaceAudience.Public;
+import org.apache.hadoop.yarn.conf.HAUtil;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.server.webproxy.WebAppProxyServlet;
+import org.apache.hadoop.yarn.util.RMHAUtils;
 
+@Public
 public class AmIpFilter implements Filter {
   private static final Log LOG = LogFactory.getLog(AmIpFilter.class);
-  
+
+  @Deprecated
   public static final String PROXY_HOST = "PROXY_HOST";
+  @Deprecated
   public static final String PROXY_URI_BASE = "PROXY_URI_BASE";
+  static final String PROXY_HOSTS = "PROXY_HOSTS";
+  static final String PROXY_HOSTS_DELIMITER = ",";
+  static final String PROXY_URI_BASES = "PROXY_URI_BASES";
+  static final String PROXY_URI_BASES_DELIMITER = ",";
   //update the proxy IP list about every 5 min
   private static final long updateInterval = 5 * 60 * 1000;
-  
-  private String proxyHost;
+
+  private String[] proxyHosts;
   private Set<String> proxyAddresses = null;
   private long lastUpdate;
-  private String proxyUriBase;
-  
+  private Map<String, String> proxyUriBases;
+
   @Override
   public void init(FilterConfig conf) throws ServletException {
-    proxyHost = conf.getInitParameter(PROXY_HOST);
-    proxyUriBase = conf.getInitParameter(PROXY_URI_BASE);
+    // Maintain for backwards compatibility
+    if (conf.getInitParameter(PROXY_HOST) != null
+        && conf.getInitParameter(PROXY_URI_BASE) != null) {
+      proxyHosts = new String[]{conf.getInitParameter(PROXY_HOST)};
+      proxyUriBases = new HashMap<String, String>(1);
+      proxyUriBases.put("dummy", conf.getInitParameter(PROXY_URI_BASE));
+    } else {
+      proxyHosts = conf.getInitParameter(PROXY_HOSTS)
+          .split(PROXY_HOSTS_DELIMITER);
+
+      String[] proxyUriBasesArr = conf.getInitParameter(PROXY_URI_BASES)
+          .split(PROXY_URI_BASES_DELIMITER);
+      proxyUriBases = new HashMap<String, String>();
+      for (String proxyUriBase : proxyUriBasesArr) {
+        try {
+          URL url = new URL(proxyUriBase);
+          proxyUriBases.put(url.getHost() + ":" + url.getPort(), proxyUriBase);
+        } catch(MalformedURLException e) {
+          LOG.warn(proxyUriBase + " does not appear to be a valid URL", e);
+        }
+      }
+    }
   }
-  
+
   protected Set<String> getProxyAddresses() throws ServletException {
     long now = System.currentTimeMillis();
     synchronized(this) {
       if(proxyAddresses == null || (lastUpdate + updateInterval) >= now) {
-        try {
-          proxyAddresses = new HashSet<String>();
-          for(InetAddress add : InetAddress.getAllByName(proxyHost)) {
-            if (LOG.isDebugEnabled()) {
-              LOG.debug("proxy address is: " + add.getHostAddress());
+        proxyAddresses = new HashSet<String>();
+        for (String proxyHost : proxyHosts) {
+          try {
+              for(InetAddress add : InetAddress.getAllByName(proxyHost)) {
+                if (LOG.isDebugEnabled()) {
+                  LOG.debug("proxy address is: " + add.getHostAddress());
+                }
+                proxyAddresses.add(add.getHostAddress());
+              }
+              lastUpdate = now;
+            } catch (UnknownHostException e) {
+              LOG.warn("Could not locate " + proxyHost + " - skipping", e);
             }
-            proxyAddresses.add(add.getHostAddress());
           }
-          lastUpdate = now;
-        } catch (UnknownHostException e) {
-          throw new ServletException("Could not locate "+proxyHost, e);
+        if (proxyAddresses.isEmpty()) {
+          throw new ServletException("Could not locate any of the proxy hosts");
         }
       }
       return proxyAddresses;
@@ -89,21 +129,22 @@ public class AmIpFilter implements Filter {
     if(!(req instanceof HttpServletRequest)) {
       throw new ServletException("This filter only works for HTTP/HTTPS");
     }
-    
+
     HttpServletRequest httpReq = (HttpServletRequest)req;
     HttpServletResponse httpResp = (HttpServletResponse)resp;
     if (LOG.isDebugEnabled()) {
       LOG.debug("Remote address for request is: " + httpReq.getRemoteAddr());
     }
     if(!getProxyAddresses().contains(httpReq.getRemoteAddr())) {
-      String redirectUrl = httpResp.encodeRedirectURL(proxyUriBase + 
+      String redirectUrl = findRedirectUrl();
+      redirectUrl = httpResp.encodeRedirectURL(redirectUrl +
           httpReq.getRequestURI());
       httpResp.sendRedirect(redirectUrl);
       return;
     }
-    
+
     String user = null;
-    
+
     if (httpReq.getCookies() != null) {
       for(Cookie c: httpReq.getCookies()) {
         if(WebAppProxyServlet.PROXY_USER_COOKIE_NAME.equals(c.getName())){
@@ -118,9 +159,30 @@ public class AmIpFilter implements Filter {
       chain.doFilter(req, resp);
     } else {
       final AmIpPrincipal principal = new AmIpPrincipal(user);
-      ServletRequest requestWrapper = new AmIpServletRequestWrapper(httpReq, 
+      ServletRequest requestWrapper = new AmIpServletRequestWrapper(httpReq,
           principal);
       chain.doFilter(requestWrapper, resp);
     }
   }
+
+  protected String findRedirectUrl() throws ServletException {
+    String addr;
+    if (proxyUriBases.size() == 1) {  // external proxy or not RM HA
+      addr = proxyUriBases.values().iterator().next();
+    } else {                          // RM HA
+      YarnConfiguration conf = new YarnConfiguration();
+      String activeRMId = RMHAUtils.findActiveRMHAId(conf);
+      String addressPropertyPrefix = YarnConfiguration.useHttps(conf)
+          ? YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS
+          : YarnConfiguration.RM_WEBAPP_ADDRESS;
+      String host = conf.get(
+          HAUtil.addSuffix(addressPropertyPrefix, activeRMId));
+      addr = proxyUriBases.get(host);
+    }
+    if (addr == null) {
+      throw new ServletException(
+          "Could not determine the proxy server for redirection");
+    }
+    return addr;
+  }
 }

+ 2 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/amfilter/TestAmFilter.java

@@ -88,6 +88,7 @@ public class TestAmFilter {
   }
 
   @Test(timeout = 5000)
+  @SuppressWarnings("deprecation")
   public void filterNullCookies() throws Exception {
     HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
 
@@ -120,6 +121,7 @@ public class TestAmFilter {
    * Test AmIpFilter
    */
   @Test(timeout = 1000)
+  @SuppressWarnings("deprecation")
   public void testFilter() throws Exception {
     Map<String, String> params = new HashMap<String, String>();
     params.put(AmIpFilter.PROXY_HOST, proxyHost);

+ 214 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-web-proxy/src/test/java/org/apache/hadoop/yarn/server/webproxy/amfilter/TestAmFilterInitializer.java

@@ -0,0 +1,214 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.yarn.server.webproxy.amfilter;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import junit.framework.TestCase;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.http.FilterContainer;
+import org.apache.hadoop.http.HttpConfig;
+import org.apache.hadoop.net.NetUtils;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
+import org.junit.Test;
+
+public class TestAmFilterInitializer extends TestCase {
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    NetUtils.addStaticResolution("host1", "172.0.0.1");
+    NetUtils.addStaticResolution("host2", "172.0.0.1");
+    NetUtils.addStaticResolution("host3", "172.0.0.1");
+    NetUtils.addStaticResolution("host4", "172.0.0.1");
+    NetUtils.addStaticResolution("host5", "172.0.0.1");
+    NetUtils.addStaticResolution("host6", "172.0.0.1");
+  }
+
+  @Test
+  public void testInitFilter() {
+    // Check PROXY_ADDRESS
+    MockFilterContainer con = new MockFilterContainer();
+    Configuration conf = new Configuration(false);
+    conf.set(YarnConfiguration.PROXY_ADDRESS, "host1:1000");
+    AmFilterInitializer afi = new MockAmFilterInitializer();
+    assertNull(con.givenParameters);
+    afi.initFilter(con, conf);
+    assertEquals(2, con.givenParameters.size());
+    assertEquals("host1", con.givenParameters.get(AmIpFilter.PROXY_HOSTS));
+    assertEquals("http://host1:1000/foo",
+        con.givenParameters.get(AmIpFilter.PROXY_URI_BASES));
+
+    // Check a single RM_WEBAPP_ADDRESS
+    con = new MockFilterContainer();
+    conf = new Configuration(false);
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS, "host2:2000");
+    afi = new MockAmFilterInitializer();
+    assertNull(con.givenParameters);
+    afi.initFilter(con, conf);
+    assertEquals(2, con.givenParameters.size());
+    assertEquals("host2", con.givenParameters.get(AmIpFilter.PROXY_HOSTS));
+    assertEquals("http://host2:2000/foo",
+        con.givenParameters.get(AmIpFilter.PROXY_URI_BASES));
+
+    // Check multiple RM_WEBAPP_ADDRESSes (RM HA)
+    con = new MockFilterContainer();
+    conf = new Configuration(false);
+    conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true);
+    conf.set(YarnConfiguration.RM_HA_IDS, "rm1,rm2,rm3");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm1", "host2:2000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm2", "host3:3000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm3", "host4:4000");
+    afi = new MockAmFilterInitializer();
+    assertNull(con.givenParameters);
+    afi.initFilter(con, conf);
+    assertEquals(2, con.givenParameters.size());
+    String[] proxyHosts = con.givenParameters.get(AmIpFilter.PROXY_HOSTS)
+        .split(AmIpFilter.PROXY_HOSTS_DELIMITER);
+    assertEquals(3, proxyHosts.length);
+    Arrays.sort(proxyHosts);
+    assertEquals("host2", proxyHosts[0]);
+    assertEquals("host3", proxyHosts[1]);
+    assertEquals("host4", proxyHosts[2]);
+    String[] proxyBases = con.givenParameters.get(AmIpFilter.PROXY_URI_BASES)
+        .split(AmIpFilter.PROXY_URI_BASES_DELIMITER);
+    assertEquals(3, proxyBases.length);
+    Arrays.sort(proxyBases);
+    assertEquals("http://host2:2000/foo", proxyBases[0]);
+    assertEquals("http://host3:3000/foo", proxyBases[1]);
+    assertEquals("http://host4:4000/foo", proxyBases[2]);
+
+    // Check multiple RM_WEBAPP_ADDRESSes (RM HA) with HTTPS
+    con = new MockFilterContainer();
+    conf = new Configuration(false);
+    conf.set(YarnConfiguration.YARN_HTTP_POLICY_KEY,
+        HttpConfig.Policy.HTTPS_ONLY.toString());
+    conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true);
+    conf.set(YarnConfiguration.RM_HA_IDS, "rm1,rm2");
+    conf.set(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS + ".rm1", "host5:5000");
+    conf.set(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS + ".rm2", "host6:6000");
+    afi = new MockAmFilterInitializer();
+    assertNull(con.givenParameters);
+    afi.initFilter(con, conf);
+    assertEquals(2, con.givenParameters.size());
+    proxyHosts = con.givenParameters.get(AmIpFilter.PROXY_HOSTS)
+        .split(AmIpFilter.PROXY_HOSTS_DELIMITER);
+    assertEquals(2, proxyHosts.length);
+    Arrays.sort(proxyHosts);
+    assertEquals("host5", proxyHosts[0]);
+    assertEquals("host6", proxyHosts[1]);
+    proxyBases = con.givenParameters.get(AmIpFilter.PROXY_URI_BASES)
+        .split(AmIpFilter.PROXY_URI_BASES_DELIMITER);
+    assertEquals(2, proxyBases.length);
+    Arrays.sort(proxyBases);
+    assertEquals("https://host5:5000/foo", proxyBases[0]);
+    assertEquals("https://host6:6000/foo", proxyBases[1]);
+  }
+
+  @Test
+  public void testGetProxyHostsAndPortsForAmFilter() {
+
+    // Check no configs given
+    Configuration conf = new Configuration(false);
+    List<String> proxyHosts =
+        WebAppUtils.getProxyHostsAndPortsForAmFilter(conf);
+    assertEquals(1, proxyHosts.size());
+    assertEquals(WebAppUtils.getResolvedRMWebAppURLWithoutScheme(conf),
+        proxyHosts.get(0));
+
+    // Check PROXY_ADDRESS has priority
+    conf = new Configuration(false);
+    conf.set(YarnConfiguration.PROXY_ADDRESS, "host1:1000");
+    conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true);
+    conf.set(YarnConfiguration.RM_HA_IDS, "rm1,rm2,rm3");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm1", "host2:2000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm2", "host3:3000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm3", "host4:4000");
+    proxyHosts = WebAppUtils.getProxyHostsAndPortsForAmFilter(conf);
+    assertEquals(1, proxyHosts.size());
+    assertEquals("host1:1000", proxyHosts.get(0));
+
+    // Check getting a single RM_WEBAPP_ADDRESS
+    conf = new Configuration(false);
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS, "host2:2000");
+    proxyHosts = WebAppUtils.getProxyHostsAndPortsForAmFilter(conf);
+    assertEquals(1, proxyHosts.size());
+    Collections.sort(proxyHosts);
+    assertEquals("host2:2000", proxyHosts.get(0));
+
+    // Check getting multiple RM_WEBAPP_ADDRESSes (RM HA)
+    conf = new Configuration(false);
+    conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true);
+    conf.set(YarnConfiguration.RM_HA_IDS, "rm1,rm2,rm3");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm1", "host2:2000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm2", "host3:3000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm3", "host4:4000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm4", "dummy");
+    conf.set(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS + ".rm1", "host5:5000");
+    conf.set(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS + ".rm2", "host6:6000");
+    proxyHosts = WebAppUtils.getProxyHostsAndPortsForAmFilter(conf);
+    assertEquals(3, proxyHosts.size());
+    Collections.sort(proxyHosts);
+    assertEquals("host2:2000", proxyHosts.get(0));
+    assertEquals("host3:3000", proxyHosts.get(1));
+    assertEquals("host4:4000", proxyHosts.get(2));
+
+    // Check getting multiple RM_WEBAPP_ADDRESSes (RM HA) with HTTPS
+    conf = new Configuration(false);
+    conf.set(YarnConfiguration.YARN_HTTP_POLICY_KEY,
+        HttpConfig.Policy.HTTPS_ONLY.toString());
+    conf.setBoolean(YarnConfiguration.RM_HA_ENABLED, true);
+    conf.set(YarnConfiguration.RM_HA_IDS, "rm1,rm2,rm3,dummy");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm1", "host2:2000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm2", "host3:3000");
+    conf.set(YarnConfiguration.RM_WEBAPP_ADDRESS + ".rm3", "host4:4000");
+    conf.set(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS + ".rm1", "host5:5000");
+    conf.set(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS + ".rm2", "host6:6000");
+    proxyHosts = WebAppUtils.getProxyHostsAndPortsForAmFilter(conf);
+    assertEquals(2, proxyHosts.size());
+    Collections.sort(proxyHosts);
+    assertEquals("host5:5000", proxyHosts.get(0));
+    assertEquals("host6:6000", proxyHosts.get(1));
+  }
+
+  class MockAmFilterInitializer extends AmFilterInitializer {
+    @Override
+    protected String getApplicationWebProxyBase() {
+      return "/foo";
+    }
+  }
+
+  class MockFilterContainer implements FilterContainer {
+    Map<String, String> givenParameters;
+
+    @Override
+    public void addFilter(String name, String classname, Map<String,
+        String> parameters) {
+      givenParameters = parameters;
+    }
+
+    @Override
+    public void addGlobalFilter(String name, String classname,
+        Map<String, String> parameters) {
+    }
+  }
+}