1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.hadoop.yarn.server.resourcemanager.webapp;
20 
21 import java.io.IOException;
22 import java.io.PrintWriter;
23 import java.net.InetSocketAddress;
24 import java.net.URI;
25 import java.net.URISyntaxException;
26 import java.util.Random;
27 import java.util.Set;
28 
29 import javax.inject.Inject;
30 import javax.inject.Singleton;
31 import javax.servlet.FilterChain;
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35 
36 import org.apache.hadoop.conf.Configuration;
37 import org.apache.hadoop.http.HtmlQuoting;
38 import org.apache.hadoop.yarn.conf.YarnConfiguration;
39 import org.apache.hadoop.yarn.server.webproxy.ProxyUriUtils;
40 import org.apache.hadoop.yarn.webapp.YarnWebParams;
41 
42 import com.google.common.collect.Sets;
43 import com.google.inject.Injector;
44 import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
45 
46 @Singleton
47 public class RMWebAppFilter extends GuiceContainer {
48 
49   private Injector injector;
50   /**
51    *
52    */
53   private static final long serialVersionUID = 1L;
54 
55   // define a set of URIs which do not need to do redirection
56   private static final Set<String> NON_REDIRECTED_URIS = Sets.newHashSet(
57       "/conf", "/stacks", "/logLevel", "/logs");
58   private String path;
59   private static final int BASIC_SLEEP_TIME = 5;
60   private static final int MAX_SLEEP_TIME = 5 * 60;
61   private static final Random randnum = new Random();
62 
63   @Inject
RMWebAppFilter(Injector injector, Configuration conf)64   public RMWebAppFilter(Injector injector, Configuration conf) {
65     super(injector);
66     this.injector=injector;
67     InetSocketAddress sock = YarnConfiguration.useHttps(conf)
68         ? conf.getSocketAddr(YarnConfiguration.RM_WEBAPP_HTTPS_ADDRESS,
69             YarnConfiguration.DEFAULT_RM_WEBAPP_HTTPS_ADDRESS,
70             YarnConfiguration.DEFAULT_RM_WEBAPP_HTTPS_PORT)
71         : conf.getSocketAddr(YarnConfiguration.RM_WEBAPP_ADDRESS,
72             YarnConfiguration.DEFAULT_RM_WEBAPP_ADDRESS,
73             YarnConfiguration.DEFAULT_RM_WEBAPP_PORT);
74 
75     path = sock.getHostName() + ":" + Integer.toString(sock.getPort());
76     path = YarnConfiguration.useHttps(conf)
77         ? "https://" + path
78         : "http://" + path;
79   }
80 
81   @Override
doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)82   public void doFilter(HttpServletRequest request,
83       HttpServletResponse response, FilterChain chain) throws IOException,
84       ServletException {
85     response.setCharacterEncoding("UTF-8");
86     String uri = HtmlQuoting.quoteHtmlChars(request.getRequestURI());
87 
88     if (uri == null) {
89       uri = "/";
90     }
91     RMWebApp rmWebApp = injector.getInstance(RMWebApp.class);
92     rmWebApp.checkIfStandbyRM();
93     if (rmWebApp.isStandby()
94         && shouldRedirect(rmWebApp, uri)) {
95 
96       String redirectPath = rmWebApp.getRedirectPath();
97 
98       if (redirectPath != null && !redirectPath.isEmpty()) {
99         redirectPath += uri;
100         String redirectMsg =
101             "This is standby RM. The redirect url is: " + redirectPath;
102         PrintWriter out = response.getWriter();
103         out.println(redirectMsg);
104         response.setHeader("Location", redirectPath);
105         response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
106         return;
107       } else {
108         boolean doRetry = true;
109         String retryIntervalStr =
110             request.getParameter(YarnWebParams.NEXT_REFRESH_INTERVAL);
111         int retryInterval = 0;
112         if (retryIntervalStr != null) {
113           try {
114             retryInterval = Integer.parseInt(retryIntervalStr.trim());
115           } catch (NumberFormatException ex) {
116             doRetry = false;
117           }
118         }
119         int next = calculateExponentialTime(retryInterval);
120 
121         String redirectUrl =
122             appendOrReplaceParamter(path + uri,
123               YarnWebParams.NEXT_REFRESH_INTERVAL + "=" + (retryInterval + 1));
124         if (redirectUrl == null || next > MAX_SLEEP_TIME) {
125           doRetry = false;
126         }
127         String redirectMsg =
128             doRetry ? "Can not find any active RM. Will retry in next " + next
129                 + " seconds." : "There is no active RM right now.";
130         redirectMsg += "\nHA Zookeeper Connection State: "
131             + rmWebApp.getHAZookeeperConnectionState();
132         PrintWriter out = response.getWriter();
133         out.println(redirectMsg);
134         if (doRetry) {
135           response.setHeader("Refresh", next + ";url=" + redirectUrl);
136           response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
137         }
138       }
139       return;
140     }
141 
142     super.doFilter(request, response, chain);
143   }
144 
shouldRedirect(RMWebApp rmWebApp, String uri)145   private boolean shouldRedirect(RMWebApp rmWebApp, String uri) {
146     return !uri.equals("/" + rmWebApp.wsName() + "/v1/cluster/info")
147         && !uri.equals("/" + rmWebApp.name() + "/cluster")
148         && !uri.startsWith(ProxyUriUtils.PROXY_BASE)
149         && !NON_REDIRECTED_URIS.contains(uri);
150   }
151 
appendOrReplaceParamter(String uri, String newQuery)152   private String appendOrReplaceParamter(String uri, String newQuery) {
153     if (uri.contains(YarnWebParams.NEXT_REFRESH_INTERVAL + "=")) {
154       return uri.replaceAll(YarnWebParams.NEXT_REFRESH_INTERVAL + "=[^&]+",
155         newQuery);
156     }
157     try {
158       URI oldUri = new URI(uri);
159       String appendQuery = oldUri.getQuery();
160       if (appendQuery == null) {
161         appendQuery = newQuery;
162       } else {
163         appendQuery += "&" + newQuery;
164       }
165 
166       URI newUri =
167           new URI(oldUri.getScheme(), oldUri.getAuthority(), oldUri.getPath(),
168             appendQuery, oldUri.getFragment());
169 
170       return newUri.toString();
171     } catch (URISyntaxException e) {
172       return null;
173     }
174   }
175 
calculateExponentialTime(int retries)176   private static int calculateExponentialTime(int retries) {
177     long baseTime = BASIC_SLEEP_TIME * (1L << retries);
178     return (int) (baseTime * (randnum.nextDouble() + 0.5));
179   }
180 }