Skip to content

Commit 57f71d3

Browse files
committed
Ref #29447: add more tests for pre-initalization
1 parent d737ba4 commit 57f71d3

7 files changed

Lines changed: 193 additions & 23 deletions

File tree

src/test/java/eu/openanalytics/containerproxy/test/helpers/ShinyProxyClient.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.fasterxml.jackson.databind.ObjectMapper;
2525
import eu.openanalytics.containerproxy.model.runtime.ProxyStatus;
2626
import eu.openanalytics.containerproxy.util.Retrying;
27+
import okhttp3.Call;
2728
import okhttp3.MediaType;
2829
import okhttp3.OkHttpClient;
2930
import okhttp3.Request;
@@ -233,4 +234,7 @@ public String getBaseUrl() {
233234
return baseUrl;
234235
}
235236

237+
public Call newCall(Request request) {
238+
return client.newCall(request);
239+
}
236240
}

src/test/java/eu/openanalytics/containerproxy/test/helpers/ShinyProxyInstance.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import java.util.HashMap;
5353
import java.util.Map;
5454
import java.util.Properties;
55+
import java.util.concurrent.atomic.AtomicReference;
5556

5657

5758
public class ShinyProxyInstance implements AutoCloseable {
@@ -98,10 +99,24 @@ public ShinyProxyInstance(String configFileName, int port, String usernameAndPas
9899

99100
client = new ShinyProxyClient(usernameAndPassword, port);
100101

102+
AtomicReference<Throwable> exception = new AtomicReference<>();
101103
thread = new Thread(() -> app = application.run());
104+
thread.setUncaughtExceptionHandler((thread, ex) -> {
105+
exception.set(ex);
106+
});
102107
thread.start();
103108

104-
boolean available = Retrying.retry((c, m) -> client.checkAlive(), 60_000, "ShinyProxyInstance available", 1, true);
109+
boolean available = Retrying.retry((c, m) -> {
110+
if (exception.get() != null) {
111+
logger.warn("Exception during startup");
112+
return true;
113+
}
114+
return client.checkAlive();
115+
}, 60_000, "ShinyProxyInstance available", 1, true);
116+
Throwable ex = exception.get();
117+
if (ex != null) {
118+
throw ex;
119+
}
105120
if (!available) {
106121
throw new TestHelperException("ShinyProxy did not become available!");
107122
} else {
@@ -152,7 +167,7 @@ public void enableCleanup() {
152167
}
153168

154169
public void stopAllApps() {
155-
for (Proxy proxy: proxyService.getAllProxies()) {
170+
for (Proxy proxy : proxyService.getAllProxies()) {
156171
proxyService.stopProxy(null, proxy, true).run();
157172
}
158173
}

src/test/java/eu/openanalytics/containerproxy/test/helpers/TestProxySharingScaler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,8 @@ public IDelegateProxyStore getDelegateProxyStore() {
5555
return delegateProxyStore;
5656
}
5757

58+
public ISeatStore getSeatStore() {
59+
return seatStore;
60+
}
61+
5862
}

src/test/java/eu/openanalytics/containerproxy/test/proxy/TestPreInitialization.java

Lines changed: 97 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,36 @@
2020
*/
2121
package eu.openanalytics.containerproxy.test.proxy;
2222

23+
import com.google.common.base.Throwables;
2324
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.IDelegateProxyStore;
2425
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.ProxySharingScaler;
26+
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.Seat;
2527
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.SeatIdKey;
2628
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.store.DelegateProxy;
29+
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.store.DelegateProxyStatus;
30+
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.store.ISeatStore;
31+
import eu.openanalytics.containerproxy.model.runtime.Container;
2732
import eu.openanalytics.containerproxy.model.runtime.Proxy;
2833
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.PublicPathKey;
2934
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.TargetIdKey;
3035
import eu.openanalytics.containerproxy.test.helpers.ContainerSetup;
3136
import eu.openanalytics.containerproxy.test.helpers.ShinyProxyInstance;
37+
import eu.openanalytics.containerproxy.test.helpers.TestHelperException;
3238
import eu.openanalytics.containerproxy.test.helpers.TestProxySharingScaler;
3339
import eu.openanalytics.containerproxy.util.Retrying;
40+
import okhttp3.Request;
41+
import okhttp3.Response;
3442
import org.junit.jupiter.api.Assertions;
43+
import org.junit.jupiter.api.Test;
3544
import org.junit.jupiter.params.ParameterizedTest;
3645
import org.junit.jupiter.params.provider.Arguments;
3746
import org.junit.jupiter.params.provider.MethodSource;
47+
import org.mandas.docker.client.DockerClient;
48+
import org.mandas.docker.client.builder.jersey.JerseyDockerClientBuilder;
49+
import org.mandas.docker.client.exceptions.DockerCertificateException;
50+
import org.mandas.docker.client.exceptions.DockerException;
3851

52+
import java.io.IOException;
3953
import java.time.Duration;
4054
import java.time.Instant;
4155
import java.util.ArrayList;
@@ -57,26 +71,40 @@ private static Stream<Arguments> backends() {
5771
@MethodSource("backends")
5872
public void simpleTest(String backend, Map<String, String> properties) {
5973
try (ContainerSetup containerSetup = new ContainerSetup(backend)) {
60-
try (ShinyProxyInstance inst = new ShinyProxyInstance("application-test-pre-initialization.yml", properties, true)) {
61-
ProxySharingScaler proxySharingScaler = inst.getBean("proxySharingScaler_simpleTest", ProxySharingScaler.class);
74+
try (ShinyProxyInstance inst = new ShinyProxyInstance("application-test-pre-initialization-1.yml", properties, true)) {
75+
TestProxySharingScaler proxySharingScaler = inst.getBean("proxySharingScaler_myApp", TestProxySharingScaler.class);
76+
IDelegateProxyStore delegateProxyStore = proxySharingScaler.getDelegateProxyStore();
77+
ISeatStore seatStore = proxySharingScaler.getSeatStore();
6278
inst.enableCleanup();
63-
String id = inst.client.startProxy("simpleTest");
79+
String id = inst.client.startProxy("myApp");
6480
Proxy proxy = inst.proxyService.getProxy(id);
6581
inst.client.testProxyReachable(proxy.getTargetId());
6682

6783
// target id should be different from proxy id
6884
Assertions.assertNotEquals(proxy.getTargetId(), proxy.getId());
6985
Assertions.assertEquals("/api/route/" + proxy.getTargetId() + "/", proxy.getRuntimeValue(PublicPathKey.inst));
7086
Assertions.assertEquals(proxy.getTargetId(), proxy.getRuntimeValue(TargetIdKey.inst));
71-
Assertions.assertNotNull(proxy.getRuntimeValue(SeatIdKey.inst));
7287

88+
// check DelegateProxy
89+
DelegateProxy delegateProxy = delegateProxyStore.getDelegateProxy(proxy.getTargetId());
90+
Assertions.assertNotNull(delegateProxy);
91+
Assertions.assertEquals(1, delegateProxy.getSeatIds().size());
92+
Assertions.assertEquals(proxy.getRuntimeValue(SeatIdKey.inst), delegateProxy.getSeatIds().stream().findFirst().get());
93+
94+
// check seat
95+
Seat seat = seatStore.getSeat(proxy.getRuntimeValue(SeatIdKey.inst));
96+
Assertions.assertEquals(proxy.getRuntimeValue(SeatIdKey.inst), seat.getId());
97+
Assertions.assertEquals(delegateProxy.getProxy().getId(), seat.getDelegateProxyId());
98+
Assertions.assertEquals(proxy.getId(), seat.getDelegatingProxyId());
99+
100+
// should have scaled-up
73101
waitUntilNoPendingSeats(proxySharingScaler);
74102
Assertions.assertEquals(1, proxySharingScaler.getNumClaimedSeats());
75103
Assertions.assertEquals(1, proxySharingScaler.getNumUnclaimedSeats());
76104

77105
// start an additional app
78106
Instant start = Instant.now();
79-
String id2 = inst.client.startProxy("simpleTest");
107+
String id2 = inst.client.startProxy("myApp");
80108
Proxy proxy2 = inst.proxyService.getProxy(id2);
81109
inst.client.testProxyReachable(proxy2.getTargetId());
82110

@@ -113,9 +141,9 @@ public void simpleTest(String backend, Map<String, String> properties) {
113141
public void testSetPublicPathPrefix(String backend, Map<String, String> properties) {
114142
ProxySharingScaler.setPublicPathPrefix("/my/custom/path/");
115143
try (ContainerSetup containerSetup = new ContainerSetup(backend)) {
116-
try (ShinyProxyInstance inst = new ShinyProxyInstance("application-test-pre-initialization.yml", properties, true)) {
144+
try (ShinyProxyInstance inst = new ShinyProxyInstance("application-test-pre-initialization-1.yml", properties, true)) {
117145
inst.enableCleanup();
118-
String id = inst.client.startProxy("simpleTest");
146+
String id = inst.client.startProxy("myApp");
119147
Proxy proxy = inst.proxyService.getProxy(id);
120148
inst.client.testProxyReachable(proxy.getTargetId());
121149

@@ -134,13 +162,13 @@ public void testSetPublicPathPrefix(String backend, Map<String, String> properti
134162
@MethodSource("backends")
135163
public void testNoContainerReUse(String backend, Map<String, String> properties) {
136164
try (ContainerSetup containerSetup = new ContainerSetup(backend)) {
137-
try (ShinyProxyInstance inst = new ShinyProxyInstance("application-test-pre-initialization.yml", properties, true)) {
165+
try (ShinyProxyInstance inst = new ShinyProxyInstance("application-test-pre-initialization-2.yml", properties, true)) {
138166
inst.enableCleanup();
139-
TestProxySharingScaler proxySharingScaler = inst.getBean("proxySharingScaler_noReUse", TestProxySharingScaler.class);
167+
TestProxySharingScaler proxySharingScaler = inst.getBean("proxySharingScaler_myApp", TestProxySharingScaler.class);
140168
proxySharingScaler.disableCleanup();
141169
IDelegateProxyStore delegateProxyStore = proxySharingScaler.getDelegateProxyStore();
142170

143-
String id = inst.client.startProxy("noReUse");
171+
String id = inst.client.startProxy("myApp");
144172
Proxy proxy = inst.proxyService.getProxy(id);
145173
inst.client.testProxyReachable(proxy.getTargetId());
146174

@@ -173,9 +201,66 @@ public void testNoContainerReUse(String backend, Map<String, String> properties)
173201
}
174202
}
175203

204+
@ParameterizedTest
205+
@MethodSource("backends")
206+
public void testReUseWithMultipleSeats(String backend, Map<String, String> properties) {
207+
TestHelperException ex = Assertions.assertThrows(TestHelperException.class,
208+
() -> new ShinyProxyInstance("application-test-pre-initialization-3.yml", properties, true),
209+
"Provided parameter values are not allowed");
210+
211+
Throwable rootCause = Throwables.getRootCause(ex);
212+
Assertions.assertInstanceOf(IllegalStateException.class, rootCause);
213+
Assertions.assertEquals("Spec myApp is invalid: when allow-container-re-use is disabled, seatsPerContainer must be exactly 1", rootCause.getMessage());
214+
}
215+
216+
@Test
217+
public void testDelegateProxyCrashed() throws DockerCertificateException, DockerException, InterruptedException, IOException {
218+
DockerClient dockerClient = new JerseyDockerClientBuilder().fromEnv().build();
219+
try (ContainerSetup containerSetup = new ContainerSetup("docker")) {
220+
try (ShinyProxyInstance inst = new ShinyProxyInstance("application-test-pre-initialization-1.yml", Map.of("proxy.container-backend", "docker"), true)) {
221+
inst.enableCleanup();
222+
String id = inst.client.startProxy("myApp");
223+
Proxy proxy = inst.proxyService.getProxy(id);
224+
inst.client.testProxyReachable(proxy.getTargetId());
225+
226+
// delete underlying container
227+
TestProxySharingScaler proxySharingScaler = inst.getBean("proxySharingScaler_myApp", TestProxySharingScaler.class);
228+
proxySharingScaler.disableCleanup();
229+
IDelegateProxyStore delegateProxyStore = proxySharingScaler.getDelegateProxyStore();
230+
DelegateProxy delegateProxy = delegateProxyStore.getDelegateProxy(proxy.getTargetId());
231+
Container container = delegateProxy.getProxy().getContainer(0);
232+
dockerClient.removeContainer(container.getId(), DockerClient.RemoveContainerParam.forceKill());
233+
234+
// try to access proxy
235+
Request request = new Request.Builder()
236+
.get()
237+
.url(inst.client.getBaseUrl() + "/api/route/" + proxy.getTargetId() + "/")
238+
.build();
239+
240+
try (Response response = inst.client.newCall(request).execute()) {
241+
Assertions.assertEquals(503, response.code());
242+
Assertions.assertEquals("{\"status\":\"fail\",\"data\":\"app_crashed\"}", response.body().string());
243+
}
244+
245+
// DelegateProxy should have no seats and marked for removal
246+
delegateProxy = delegateProxyStore.getDelegateProxy(proxy.getTargetId());
247+
Assertions.assertEquals(DelegateProxyStatus.ToRemove, delegateProxy.getDelegateProxyStatus());
248+
Assertions.assertTrue( delegateProxy.getSeatIds().isEmpty());
249+
250+
// enable cleanup
251+
proxySharingScaler.enableCleanup();
252+
253+
// old proxy should get cleaned up
254+
waitUntilNumberOfDelegateProxies(proxySharingScaler, 1);
255+
DelegateProxy newDelegateProxy = delegateProxyStore.getAllDelegateProxies().stream().findFirst().get();
256+
// should be a different proxy, old one should have been removed
257+
Assertions.assertNotEquals(newDelegateProxy.getProxy().getId(), delegateProxy.getProxy().getId());
258+
Assertions.assertNotEquals(newDelegateProxy.getProxy().getTargetId(), delegateProxy.getProxy().getTargetId());
259+
Assertions.assertEquals(1, newDelegateProxy.getSeatIds().size());
260+
}
261+
}
262+
}
176263

177-
// TODO test re-use with more than one seats
178-
// TODO test crashed
179264
// TODO test scaleDownDelay
180265
// TODO test minimumSeatsAvailable
181266
// TODO test config change
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
spring:
2+
session:
3+
store-type: none
4+
data:
5+
redis:
6+
repositories:
7+
enabled: false
8+
proxy:
9+
authentication: simple
10+
heartbeat-timeout: -1
11+
default-stop-proxy-on-logout: false
12+
13+
users:
14+
- name: demo
15+
password: demo
16+
groups:
17+
- group1
18+
- group2
19+
- name: demo2
20+
password: demo2
21+
22+
docker:
23+
url: http://localhost:2375
24+
25+
26+
specs:
27+
- id: myApp
28+
minimum-seats-available: 1
29+
container-specs:
30+
- image: "openanalytics/shinyproxy-integration-test-app"
31+
cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ]
32+
port-mapping:
33+
- name: default
34+
port: 3838

src/test/resources/application-test-pre-initialization.yml renamed to src/test/resources/application-test-pre-initialization-2.yml

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,7 @@ proxy:
2424

2525

2626
specs:
27-
- id: simpleTest
28-
minimum-seats-available: 1
29-
container-specs:
30-
- image: "openanalytics/shinyproxy-integration-test-app"
31-
cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ]
32-
port-mapping:
33-
- name: default
34-
port: 3838
35-
- id: noReUse
27+
- id: myApp
3628
minimum-seats-available: 1
3729
allow-container-re-use: false
3830
container-specs:
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
spring:
2+
session:
3+
store-type: none
4+
data:
5+
redis:
6+
repositories:
7+
enabled: false
8+
proxy:
9+
authentication: simple
10+
heartbeat-timeout: -1
11+
default-stop-proxy-on-logout: false
12+
13+
users:
14+
- name: demo
15+
password: demo
16+
groups:
17+
- group1
18+
- group2
19+
- name: demo2
20+
password: demo2
21+
22+
docker:
23+
url: http://localhost:2375
24+
25+
26+
specs:
27+
- id: myApp
28+
minimum-seats-available: 1
29+
seats-per-container: 2
30+
allow-container-re-use: false
31+
container-specs:
32+
- image: "openanalytics/shinyproxy-integration-test-app"
33+
cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ]
34+
port-mapping:
35+
- name: default
36+
port: 3838

0 commit comments

Comments
 (0)