Skip to content

Commit ceb3e14

Browse files
committed
Fix #34197, #34198: show logs for failed apps
1 parent 165918c commit ceb3e14

8 files changed

Lines changed: 281 additions & 29 deletions

File tree

src/main/java/eu/openanalytics/containerproxy/backend/AbstractContainerBackend.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import eu.openanalytics.containerproxy.service.StructuredLogger;
3939
import org.slf4j.Logger;
4040
import org.slf4j.LoggerFactory;
41+
import org.springframework.context.ApplicationEventPublisher;
4142
import org.springframework.context.annotation.Lazy;
4243
import org.springframework.core.env.Environment;
4344
import org.springframework.security.core.Authentication;
@@ -81,6 +82,8 @@ public abstract class AbstractContainerBackend implements IContainerBackend {
8182
protected AppRecoveryService appRecoveryService;
8283
@Inject
8384
protected IdentifierService identifierService;
85+
@Inject
86+
protected ApplicationEventPublisher applicationEventPublisher;
8487
private boolean useInternalNetwork;
8588
private boolean privileged;
8689
private String defaultTargetProtocol;

src/main/java/eu/openanalytics/containerproxy/backend/docker/DockerEngineBackend.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package eu.openanalytics.containerproxy.backend.docker;
2222

2323
import eu.openanalytics.containerproxy.ContainerFailedToStartException;
24+
import eu.openanalytics.containerproxy.event.NewProxyEvent;
2425
import eu.openanalytics.containerproxy.model.runtime.Container;
2526
import eu.openanalytics.containerproxy.model.runtime.ExistingContainerInfo;
2627
import eu.openanalytics.containerproxy.model.runtime.PortMappings;
@@ -146,7 +147,7 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
146147

147148
List<HostConfig.DeviceRequest> deviceRequests = new ArrayList<>();
148149
for (DockerDeviceRequest deviceRequest : spec.getDockerDeviceRequests()) {
149-
HostConfig.DeviceRequest.Builder builder= HostConfig.DeviceRequest.builder();
150+
HostConfig.DeviceRequest.Builder builder = HostConfig.DeviceRequest.builder();
150151
deviceRequest.getDriver().ifPresent(builder::driver);
151152
deviceRequest.getCount().ifPresent(builder::count);
152153
deviceRequest.getDeviceIds().ifPresent(builder::deviceIds);
@@ -181,6 +182,10 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
181182

182183
proxyStartupLogBuilder.startingContainer(initialContainer.getIndex());
183184
String containerName = spec.getResourceName().getValueOrDefault("sp-container-" + proxy.getId() + "-" + initialContainer.getIndex());
185+
rContainerBuilder.addRuntimeValue(new RuntimeValue(BackendContainerNameKey.inst, new BackendContainerName(containerName)), false);
186+
if (user != null) {
187+
applicationEventPublisher.publishEvent(new NewProxyEvent(proxy.toBuilder().updateContainer(rContainerBuilder.build()).build(), user));
188+
}
184189

185190
ContainerCreation containerCreation = dockerClient.createContainer(containerConfig, containerName);
186191
rContainerBuilder.id(containerCreation.id());
@@ -192,7 +197,6 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
192197
}
193198

194199
dockerClient.startContainer(containerCreation.id());
195-
rContainerBuilder.addRuntimeValue(new RuntimeValue(BackendContainerNameKey.inst, new BackendContainerName(containerName)), false);
196200
proxyStartupLogBuilder.containerStarted(initialContainer.getIndex());
197201

198202
Container rContainer = rContainerBuilder.build();

src/main/java/eu/openanalytics/containerproxy/backend/ecs/EcsBackend.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import eu.openanalytics.containerproxy.ContainerFailedToStartException;
2424
import eu.openanalytics.containerproxy.backend.AbstractContainerBackend;
25+
import eu.openanalytics.containerproxy.event.NewProxyEvent;
2526
import eu.openanalytics.containerproxy.model.runtime.Container;
2627
import eu.openanalytics.containerproxy.model.runtime.ExistingContainerInfo;
2728
import eu.openanalytics.containerproxy.model.runtime.PortMappings;
@@ -226,6 +227,11 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
226227

227228
String taskArn = runTaskResponse.tasks().get(0).taskArn();
228229

230+
rContainerBuilder.addRuntimeValue(new RuntimeValue(BackendContainerNameKey.inst, new BackendContainerName(taskArn)), false);
231+
if (user != null) {
232+
applicationEventPublisher.publishEvent(new NewProxyEvent(proxy.toBuilder().updateContainer(rContainerBuilder.build()).build(), user));
233+
}
234+
229235
boolean serviceReady = Retrying.retry((currentAttempt, maxAttempts) -> {
230236
DescribeTasksResponse response = ecsClient.describeTasks(builder -> builder
231237
.cluster(cluster)
@@ -244,7 +250,6 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
244250
proxyStartupLogBuilder.containerStarted(initialContainer.getIndex());
245251

246252
String image = ecsClient.describeTasks(builder -> builder.cluster(cluster).tasks(taskArn)).tasks().get(0).containers().get(0).image();
247-
rContainerBuilder.addRuntimeValue(new RuntimeValue(BackendContainerNameKey.inst, new BackendContainerName(taskArn)), false);
248253
rContainerBuilder.addRuntimeValue(new RuntimeValue(ContainerImageKey.inst, image), false);
249254

250255
if (!serviceReady) {

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.common.base.Splitter;
2828
import eu.openanalytics.containerproxy.ContainerFailedToStartException;
2929
import eu.openanalytics.containerproxy.backend.AbstractContainerBackend;
30+
import eu.openanalytics.containerproxy.event.NewProxyEvent;
3031
import eu.openanalytics.containerproxy.model.runtime.Container;
3132
import eu.openanalytics.containerproxy.model.runtime.ExistingContainerInfo;
3233
import eu.openanalytics.containerproxy.model.runtime.PortMappings;
@@ -92,7 +93,7 @@
9293
import org.apache.commons.compress.utils.IOUtils;
9394
import org.apache.commons.lang3.StringUtils;
9495
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
95-
import org.springframework.data.util.Pair;
96+
import org.springframework.context.ApplicationEventPublisher;
9697
import org.springframework.security.core.Authentication;
9798
import org.springframework.stereotype.Component;
9899

@@ -336,6 +337,10 @@ public Proxy startContainer(Authentication user, Container initialContainer, Con
336337
// set the BackendContainerName now, so that the pod can be deleted in case other steps of this function fails
337338
rContainerBuilder.addRuntimeValue(new RuntimeValue(BackendContainerNameKey.inst, new BackendContainerName(effectiveKubeNamespace, patchedPod.getMetadata().getName())), false);
338339

340+
if (user != null) {
341+
applicationEventPublisher.publishEvent(new NewProxyEvent(proxy.toBuilder().updateContainer(rContainerBuilder.build()).build(), user));
342+
}
343+
339344
// create additional manifests -> use the effective (i.e. patched) namespace if no namespace is provided
340345
createAdditionalManifests(user, proxySpec, proxy, specExtension, effectiveKubeNamespace, initialContainer);
341346

@@ -427,10 +432,10 @@ private void logKubernetesWarnings(Proxy proxy, String podNamespace, String podN
427432
try {
428433
events = kubeClient.v1().events().inNamespace(podNamespace)
429434
.withInvolvedObject(new ObjectReferenceBuilder()
430-
.withKind("Pod")
431-
.withName(podName)
432-
.withNamespace(podNamespace)
433-
.build()).list().getItems();
435+
.withKind("Pod")
436+
.withName(podName)
437+
.withNamespace(podNamespace)
438+
.build()).list().getItems();
434439
} catch (KubernetesClientException ex) {
435440
if (ex.getCode() == 403) {
436441
log.warn("Cannot parse events of pod because of insufficient permissions. Give the ShinyProxy ServiceAccount permission to get events of pods in order to show Kubernetes warnings in ShinyProxy logs.");
@@ -473,10 +478,10 @@ private void parseKubernetesEvents(int containerIdx, Pod pod, ProxyStartupLog.Pr
473478
try {
474479
events = kubeClient.v1().events().inNamespace(pod.getMetadata().getNamespace())
475480
.withInvolvedObject(new ObjectReferenceBuilder()
476-
.withKind("Pod")
477-
.withName(pod.getMetadata().getName())
478-
.withNamespace(pod.getMetadata().getNamespace())
479-
.build()).list().getItems();
481+
.withKind("Pod")
482+
.withName(pod.getMetadata().getName())
483+
.withNamespace(pod.getMetadata().getNamespace())
484+
.build()).list().getItems();
480485
} catch (KubernetesClientException ex) {
481486
if (ex.getCode() == 403) {
482487
log.warn("Cannot parse events of pod because of insufficient permissions. If fine-grained statistics are desired, give the ShinyProxy ServiceAccount permission to events of pods.");
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2024 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
21+
package eu.openanalytics.containerproxy.event;
22+
23+
import com.fasterxml.jackson.annotation.JsonCreator;
24+
import com.fasterxml.jackson.annotation.JsonIgnore;
25+
import com.fasterxml.jackson.annotation.JsonProperty;
26+
import eu.openanalytics.containerproxy.model.runtime.Proxy;
27+
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.BackendContainerName;
28+
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.BackendContainerNameKey;
29+
import lombok.AccessLevel;
30+
import lombok.EqualsAndHashCode;
31+
import lombok.NoArgsConstructor;
32+
import lombok.Value;
33+
import org.springframework.security.core.Authentication;
34+
35+
@Value
36+
@EqualsAndHashCode(callSuper = true)
37+
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) // Jackson deserialize compatibility
38+
public class NewProxyEvent extends BridgeableEvent {
39+
40+
String proxyId;
41+
String userId;
42+
String specId;
43+
String instance;
44+
Long createdTimestamp;
45+
BackendContainerName backendContainerName;
46+
@JsonIgnore
47+
Authentication authentication;
48+
49+
@JsonCreator
50+
public NewProxyEvent(@JsonProperty("source") String source,
51+
@JsonProperty("proxyId") String proxyId,
52+
@JsonProperty("userId") String userId,
53+
@JsonProperty("specId") String specId,
54+
@JsonProperty("instance") String instance,
55+
@JsonProperty("createdTimestamp") Long createdTimestamp,
56+
@JsonProperty("backendContainerName") BackendContainerName backendContainerName) {
57+
this(source, proxyId, userId, specId, instance, createdTimestamp, backendContainerName, null);
58+
}
59+
60+
public NewProxyEvent(String source,
61+
String proxyId,
62+
String userId,
63+
String specId,
64+
String instance,
65+
long createdTimestamp,
66+
BackendContainerName backendContainerName,
67+
Authentication authentication) {
68+
super(source);
69+
this.proxyId = proxyId;
70+
this.userId = userId;
71+
this.specId = specId;
72+
this.instance = instance;
73+
this.createdTimestamp = createdTimestamp;
74+
this.backendContainerName = backendContainerName;
75+
this.authentication = authentication;
76+
}
77+
78+
public NewProxyEvent(Proxy proxy, Authentication authentication) {
79+
this(SOURCE_NOT_AVAILABLE,
80+
proxy.getId(),
81+
proxy.getUserId(),
82+
proxy.getSpecId(),
83+
proxy.getRuntimeValue("SHINYPROXY_APP_INSTANCE"),
84+
proxy.getCreatedTimestamp(),
85+
proxy.getContainers().isEmpty() ? null : proxy.getContainers().get(0).getRuntimeObjectOrNull(BackendContainerNameKey.inst),
86+
authentication);
87+
}
88+
89+
@Override
90+
public NewProxyEvent withSource(String source) {
91+
return new NewProxyEvent(source, proxyId, userId, specId, instance, createdTimestamp, backendContainerName);
92+
}
93+
94+
}

src/main/java/eu/openanalytics/containerproxy/event/ProxyStartFailedEvent.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
import com.fasterxml.jackson.annotation.JsonCreator;
2525
import com.fasterxml.jackson.annotation.JsonProperty;
2626
import eu.openanalytics.containerproxy.model.runtime.Proxy;
27+
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.BackendContainerName;
28+
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.BackendContainerNameKey;
2729
import lombok.AccessLevel;
28-
import lombok.AllArgsConstructor;
2930
import lombok.EqualsAndHashCode;
3031
import lombok.NoArgsConstructor;
3132
import lombok.Value;
32-
import lombok.With;
3333

3434
@Value
3535
@EqualsAndHashCode(callSuper = true)
@@ -39,24 +39,41 @@ public class ProxyStartFailedEvent extends BridgeableEvent {
3939
String proxyId;
4040
String userId;
4141
String specId;
42+
String instance;
43+
Long createdTimestamp;
44+
BackendContainerName backendContainerName;
4245

4346
@JsonCreator
4447
public ProxyStartFailedEvent(@JsonProperty("source") String source,
4548
@JsonProperty("proxyId") String proxyId,
4649
@JsonProperty("userId") String userId,
47-
@JsonProperty("specId") String specId) {
50+
@JsonProperty("specId") String specId,
51+
@JsonProperty("instance") String instance,
52+
@JsonProperty("createdTimestamp") long createdTimestamp,
53+
@JsonProperty("backendContainerName") BackendContainerName backendContainerName) {
4854
super(source);
4955
this.proxyId = proxyId;
5056
this.userId = userId;
5157
this.specId = specId;
58+
this.instance = instance;
59+
this.createdTimestamp = createdTimestamp;
60+
this.backendContainerName = backendContainerName;
5261
}
5362

5463
public ProxyStartFailedEvent(Proxy proxy) {
55-
this(SOURCE_NOT_AVAILABLE, proxy.getId(), proxy.getUserId(), proxy.getSpecId());
64+
this(SOURCE_NOT_AVAILABLE,
65+
proxy.getId(),
66+
proxy.getUserId(),
67+
proxy.getSpecId(),
68+
proxy.getRuntimeValue("SHINYPROXY_APP_INSTANCE"),
69+
proxy.getCreatedTimestamp(),
70+
proxy.getContainers().isEmpty() ? null : proxy.getContainers().get(0).getRuntimeObjectOrNull(BackendContainerNameKey.inst)
71+
);
5672
}
5773

5874
@Override
5975
public ProxyStartFailedEvent withSource(String source) {
60-
return new ProxyStartFailedEvent(source, proxyId, userId, specId);
76+
return new ProxyStartFailedEvent(source, proxyId, userId, specId, instance, createdTimestamp, backendContainerName);
6177
}
78+
6279
}

src/main/java/eu/openanalytics/containerproxy/event/ProxyStopEvent.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import com.fasterxml.jackson.annotation.JsonProperty;
2626
import eu.openanalytics.containerproxy.model.runtime.Proxy;
2727
import eu.openanalytics.containerproxy.model.runtime.ProxyStopReason;
28+
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.BackendContainerName;
29+
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.BackendContainerNameKey;
2830
import lombok.AccessLevel;
2931
import lombok.EqualsAndHashCode;
3032
import lombok.NoArgsConstructor;
@@ -41,8 +43,11 @@ public class ProxyStopEvent extends BridgeableEvent {
4143
String proxyId;
4244
String userId;
4345
String specId;
44-
ProxyStopReason proxyStopReason;
46+
String instance;
47+
Long createdTimestamp;
48+
BackendContainerName backendContainerName;
4549
Duration usageTime;
50+
ProxyStopReason proxyStopReason;
4651
@JsonIgnore
4752
Authentication authentication;
4853

@@ -51,22 +56,31 @@ public ProxyStopEvent(@JsonProperty("source") String source,
5156
@JsonProperty("proxyId") String proxyId,
5257
@JsonProperty("userId") String userId,
5358
@JsonProperty("specId") String specId,
59+
@JsonProperty("instance") String instance,
60+
@JsonProperty("createdTimestamp") Long createdTimestamp,
61+
@JsonProperty("backendContainerName") BackendContainerName backendContainerName,
5462
@JsonProperty("proxyStopReason") ProxyStopReason proxyStopReason,
5563
@JsonProperty("usageTime") Duration usageTime) {
56-
this(source, proxyId, userId, specId, proxyStopReason, usageTime, null);
64+
this(source, proxyId, userId, specId, instance, createdTimestamp, backendContainerName, proxyStopReason, usageTime, null);
5765
}
5866

5967
public ProxyStopEvent(String source,
6068
String proxyId,
6169
String userId,
6270
String specId,
71+
String instance,
72+
Long createdTimestamp,
73+
BackendContainerName backendContainerName,
6374
ProxyStopReason proxyStopReason,
6475
Duration usageTime,
6576
Authentication authentication) {
6677
super(source);
6778
this.proxyId = proxyId;
6879
this.userId = userId;
6980
this.specId = specId;
81+
this.instance = instance;
82+
this.createdTimestamp = createdTimestamp;
83+
this.backendContainerName = backendContainerName;
7084
this.proxyStopReason = proxyStopReason;
7185
this.usageTime = usageTime;
7286
this.authentication = authentication;
@@ -77,14 +91,25 @@ public ProxyStopEvent(Proxy proxy, ProxyStopReason proxyStopReason, Authenticati
7791
proxy.getId(),
7892
proxy.getUserId(),
7993
proxy.getSpecId(),
94+
proxy.getRuntimeValue("SHINYPROXY_APP_INSTANCE"),
95+
proxy.getCreatedTimestamp(),
96+
proxy.getContainers().isEmpty() ? null : proxy.getContainers().get(0).getRuntimeObjectOrNull(BackendContainerNameKey.inst),
8097
proxyStopReason,
8198
proxy.getStartupTimestamp() == 0 ? null : Duration.ofMillis(System.currentTimeMillis() - proxy.getStartupTimestamp()),
8299
authentication);
83100
}
84101

85102
@Override
86103
public ProxyStopEvent withSource(String source) {
87-
return new ProxyStopEvent(source, proxyId, userId, specId, proxyStopReason, usageTime);
104+
return new ProxyStopEvent(source,
105+
proxyId,
106+
userId,
107+
specId,
108+
instance,
109+
createdTimestamp,
110+
backendContainerName,
111+
proxyStopReason,
112+
usageTime);
88113
}
89114

90115
}

0 commit comments

Comments
 (0)