Skip to content

Commit d570255

Browse files
committed
Fix #28788: detect when app is unavailable
1 parent fb64088 commit d570255

4 files changed

Lines changed: 77 additions & 23 deletions

File tree

src/main/java/eu/openanalytics/containerproxy/api/ProxyRouteController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public void route(HttpServletRequest request, HttpServletResponse response) {
6363
}
6464

6565
if (hasAccess) {
66-
mappingManager.dispatchAsync(mapping, request, response);
66+
mappingManager.dispatchAsync(proxyId, mapping, request, response);
6767
} else {
6868
response.setStatus(403);
6969
response.getWriter().write("Not authorized to access this proxy");

src/main/java/eu/openanalytics/containerproxy/backend/kubernetes/KubernetesBackend.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,9 @@ private List<GenericKubernetesResource> parseAdditionalManifests(String specId,
535535
// therefore we overwrite this namespace with the namespace of the pod.
536536
fullObject.getMetadata().setNamespace(namespace);
537537
}
538+
if (fullObject.getMetadata().getLabels() == null) {
539+
fullObject.getMetadata().setLabels(new HashMap<>());
540+
}
538541
fullObject.getMetadata().getLabels().put("openanalytics.eu/sp-additional-manifest", "true");
539542
fullObject.getMetadata().getLabels().put("openanalytics.eu/sp-realm", identifierService.realmId); // TODO
540543
fullObject.getMetadata().getLabels().put("openanalytics.eu/sp-spec-id", specId);

src/main/java/eu/openanalytics/containerproxy/service/ProxyService.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import javax.annotation.PostConstruct;
3939
import javax.annotation.PreDestroy;
4040
import javax.inject.Inject;
41-
import javax.ws.rs.HEAD;
4241

4342
import eu.openanalytics.containerproxy.event.ProxyStartFailedEvent;
4443
import eu.openanalytics.containerproxy.event.ProxyStartEvent;
@@ -325,6 +324,15 @@ public void stopProxy(Proxy proxy, boolean async, boolean ignoreAccessControl) {
325324
}
326325
}
327326

327+
public void stopCrashedProxy(String proxyId) {
328+
Proxy proxy = getProxy(proxyId);
329+
if (proxy == null) {
330+
return;
331+
}
332+
log.warn(String.format("Proxy crashed [user: %s] [spec: %s] [id: %s]", proxy.getUserId(), proxy.getSpec().getId(), proxy.getId()));
333+
stopProxy(proxy, true, true);
334+
}
335+
328336
/**
329337
* Add existing Proxy to the ProxyService.
330338
* This is used by the AppRecovery feature.

src/main/java/eu/openanalytics/containerproxy/util/ProxyMappingManager.java

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,10 @@
2020
*/
2121
package eu.openanalytics.containerproxy.util;
2222

23-
import java.io.IOException;
24-
import java.lang.reflect.Field;
25-
import java.net.URI;
26-
import java.nio.ByteBuffer;
27-
import java.util.HashMap;
28-
import java.util.Map;
29-
import java.util.Map.Entry;
30-
import java.util.concurrent.TimeUnit;
31-
32-
import javax.inject.Inject;
33-
import javax.servlet.ServletException;
34-
import javax.servlet.http.HttpServletRequest;
35-
import javax.servlet.http.HttpServletResponse;
36-
37-
import org.springframework.stereotype.Component;
38-
23+
import eu.openanalytics.containerproxy.service.ProxyService;
3924
import eu.openanalytics.containerproxy.service.hearbeat.HeartbeatService;
25+
import io.undertow.io.Sender;
26+
import io.undertow.server.DefaultResponseListener;
4027
import io.undertow.server.HttpHandler;
4128
import io.undertow.server.HttpServerExchange;
4229
import io.undertow.server.handlers.PathHandler;
@@ -47,7 +34,23 @@
4734
import io.undertow.server.handlers.proxy.ProxyHandler;
4835
import io.undertow.servlet.handlers.ServletRequestContext;
4936
import io.undertow.util.AttachmentKey;
37+
import io.undertow.util.Headers;
5038
import io.undertow.util.PathMatcher;
39+
import io.undertow.util.StatusCodes;
40+
import org.springframework.stereotype.Component;
41+
42+
import javax.inject.Inject;
43+
import javax.servlet.ServletException;
44+
import javax.servlet.http.HttpServletRequest;
45+
import javax.servlet.http.HttpServletResponse;
46+
import java.io.IOException;
47+
import java.lang.reflect.Field;
48+
import java.net.URI;
49+
import java.nio.ByteBuffer;
50+
import java.util.HashMap;
51+
import java.util.Map;
52+
import java.util.Map.Entry;
53+
import java.util.concurrent.TimeUnit;
5154

5255
/**
5356
* This component keeps track of which proxy mappings (i.e. URL endpoints) are currently registered,
@@ -58,14 +61,18 @@ public class ProxyMappingManager {
5861

5962
private static final String PROXY_INTERNAL_ENDPOINT = "/proxy_endpoint";
6063
private static final AttachmentKey<ProxyMappingManager> ATTACHMENT_KEY_DISPATCHER = AttachmentKey.create(ProxyMappingManager.class);
61-
64+
private static final AttachmentKey<ProxyIdAttachment> ATTACHMENT_KEY_PROXY_ID = AttachmentKey.create(ProxyIdAttachment.class);
65+
6266
private PathHandler pathHandler;
6367

6468
private Map<String, String> mappings = new HashMap<>();
6569

6670
@Inject
6771
private HeartbeatService heartbeatService;
68-
72+
73+
@Inject
74+
private ProxyService proxyService;
75+
6976
public synchronized HttpHandler createHttpHandler(HttpHandler defaultHandler) {
7077
if (pathHandler == null) {
7178
pathHandler = new ProxyPathHandler(defaultHandler);
@@ -118,24 +125,51 @@ public String getProxyId(String mapping) {
118125
*
119126
* Note that clients can never access a proxy handler directly (for security reasons).
120127
* Dispatching is the only allowed method to access proxy handlers.
121-
*
128+
*
129+
* @param proxyId The id of the proxy
122130
* @param mapping The target mapping to dispatch to.
123131
* @param request The request to dispatch.
124132
* @param response The response corresponding to the request.
125133
* @throws IOException If the dispatch fails for an I/O reason.
126134
* @throws ServletException If the dispatch fails for any other reason.
127135
*/
128-
public void dispatchAsync(String mapping, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
136+
public void dispatchAsync(String proxyId, String mapping, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
129137
HttpServerExchange exchange = ServletRequestContext.current().getExchange();
130138
exchange.putAttachment(ATTACHMENT_KEY_DISPATCHER, this);
139+
exchange.putAttachment(ATTACHMENT_KEY_PROXY_ID, new ProxyIdAttachment(proxyId));
131140

132141
String queryString = request.getQueryString();
133142
queryString = (queryString == null) ? "" : "?" + queryString;
134143
String targetPath = PROXY_INTERNAL_ENDPOINT + "/" + mapping + queryString;
135-
144+
145+
exchange.addDefaultResponseListener(defaultResponseListener);
136146
request.startAsync();
137147
request.getRequestDispatcher(targetPath).forward(request, response);
138148
}
149+
150+
private final DefaultResponseListener defaultResponseListener = responseExchange -> {
151+
if (!responseExchange.isResponseChannelAvailable()) {
152+
return false;
153+
}
154+
if (responseExchange.getStatusCode() == StatusCodes.SERVICE_UNAVAILABLE) {
155+
final String errorPage = "{\"status\":\"error\", \"message\":\"app_crashed\"}";
156+
responseExchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "" + errorPage.length());
157+
responseExchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");
158+
Sender sender = responseExchange.getResponseSender();
159+
sender.send(errorPage);
160+
161+
ProxyIdAttachment proxyIdAttachment = responseExchange.getAttachment(ATTACHMENT_KEY_PROXY_ID);
162+
if (proxyIdAttachment != null) {
163+
try {
164+
proxyService.stopCrashedProxy(proxyIdAttachment.proxyId);
165+
} catch (Throwable t) {
166+
// ignore in order to complete request
167+
}
168+
}
169+
return true;
170+
}
171+
return false;
172+
};
139173

140174
private static class ProxyPathHandler extends PathHandler {
141175

@@ -161,4 +195,13 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
161195
}
162196
}
163197
}
198+
199+
private static class ProxyIdAttachment {
200+
final String proxyId;
201+
202+
public ProxyIdAttachment(String proxyId) {
203+
this.proxyId = proxyId;
204+
}
205+
}
206+
164207
}

0 commit comments

Comments
 (0)