Skip to content

Commit f2c9e73

Browse files
committed
Ref #29447: add tests for pre-initalization
1 parent 2535e99 commit f2c9e73

8 files changed

Lines changed: 175 additions & 12 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ public ResponseEntity<ApiResponse<Proxy>> startProxy(@PathVariable String proxyS
269269
}
270270

271271
private String getPublicPath(String proxyId) {
272-
return contextPathHelper.withEndingSlash() + "/api/route/" + proxyId;
272+
return contextPathHelper.withEndingSlash() + "api/route/" + proxyId;
273273
}
274274

275275
public static class ParametersBody {

src/main/java/eu/openanalytics/containerproxy/backend/dispatcher/proxysharing/ProxySharingDispatcher.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ public class ProxySharingDispatcher implements IProxyDispatcher {
8888
private IProxyStore proxyStore;
8989
@Inject
9090
private Environment environment;
91-
@Lazy
9291
@Autowired(required = false)
9392
private ProxySharingMicrometer proxySharingMicrometer = null;
9493

@@ -172,10 +171,7 @@ public Proxy startProxy(Authentication user, Proxy proxy, ProxySpec spec, ProxyS
172171
Proxy.ProxyBuilder resultProxy = proxy.toBuilder();
173172
resultProxy.targetId(delegateProxy.getId());
174173
resultProxy.addTargets(delegateProxy.getTargets());
175-
String publicPath = proxy.getRuntimeObjectOrNull(PublicPathKey.inst);
176-
if (publicPath != null) {
177-
resultProxy.addRuntimeValue(new RuntimeValue(PublicPathKey.inst, publicPath.replaceAll(proxy.getId(), delegateProxy.getId())), true);
178-
}
174+
resultProxy.addRuntimeValue(new RuntimeValue(PublicPathKey.inst, delegateProxy.getRuntimeValue(PublicPathKey.inst)), true);
179175
resultProxy.addRuntimeValue(new RuntimeValue(TargetIdKey.inst, delegateProxy.getId()), true);
180176
resultProxy.addRuntimeValue(new RuntimeValue(SeatIdKey.inst, seat.getId()), true);
181177

src/main/java/eu/openanalytics/containerproxy/backend/dispatcher/proxysharing/ProxySharingScaler.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.RealmIdKey;
4747
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.RuntimeValue;
4848
import eu.openanalytics.containerproxy.model.spec.ContainerSpec;
49-
import eu.openanalytics.containerproxy.model.spec.ISpecExtension;
5049
import eu.openanalytics.containerproxy.model.spec.ProxySpec;
5150
import eu.openanalytics.containerproxy.service.IdentifierService;
5251
import eu.openanalytics.containerproxy.service.LogService;
@@ -74,9 +73,7 @@
7473
import java.util.ArrayList;
7574
import java.util.Collection;
7675
import java.util.Collections;
77-
import java.util.HashMap;
7876
import java.util.List;
79-
import java.util.Map;
8077
import java.util.Objects;
8178
import java.util.Set;
8279
import java.util.UUID;
@@ -593,6 +590,12 @@ public Long getNumClaimedSeats() {
593590
return seatStore.getNumClaimedSeats();
594591
}
595592

593+
public void stopAll() {
594+
for (DelegateProxy delegateProxy : delegateProxyStore.getAllDelegateProxies()) {
595+
containerBackend.stopProxy(delegateProxy.getProxy());
596+
}
597+
}
598+
596599
private String getPublicPath(String targetId) {
597600
return publicPathPrefix + targetId + "/";
598601
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import eu.openanalytics.containerproxy.model.spec.ContainerSpec;
3636
import eu.openanalytics.containerproxy.model.spec.ProxySpec;
3737
import org.mandas.docker.client.DockerClient;
38+
import org.mandas.docker.client.exceptions.ConflictException;
3839
import org.mandas.docker.client.exceptions.ContainerNotFoundException;
3940
import org.mandas.docker.client.exceptions.DockerException;
4041
import org.mandas.docker.client.exceptions.NotFoundException;
@@ -212,6 +213,8 @@ protected void doStopProxy(Proxy proxy) throws Exception {
212213
dockerClient.removeContainer(container.getId(), DockerClient.RemoveContainerParam.forceKill());
213214
} catch (ContainerNotFoundException e) {
214215
// ignore, container is already removed
216+
} catch (ConflictException e) {
217+
// ignore, container is currently being removed
215218
}
216219
}
217220
portAllocator.release(proxy.getId());

src/main/java/eu/openanalytics/containerproxy/service/hearbeat/HeartbeatService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public void heartbeatReceived(@Nonnull HeartbeatService.HeartbeatSource heartbea
110110
for (IHeartbeatProcessor heartbeatProcessor : heartbeatProcessors) {
111111
heartbeatProcessor.heartbeatReceived(heartbeatSource, proxy, sessionId);
112112
}
113-
if (log.isDebugEnabled()) log.debug(String.format("Heartbeat received [proxyId: %s] [source: %s]", proxy, heartbeatSource));
113+
if (log.isDebugEnabled()) log.debug(String.format("Heartbeat received [proxyId: %s] [source: %s]", proxy.getId(), heartbeatSource));
114114
}
115115

116116
public long getHeartbeatRate() {

src/test/java/eu/openanalytics/containerproxy/test/e2e/app_recovery/TestAppRecovery.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import org.junit.jupiter.params.provider.MethodSource;
3131

3232
import javax.json.JsonObject;
33-
import java.util.HashMap;
3433
import java.util.HashSet;
3534
import java.util.Map;
3635
import java.util.stream.Stream;
@@ -53,7 +52,11 @@ private static Stream<Arguments> backends() {
5352
}
5453

5554
private static Stream<Arguments> new_app_should_work_after_recovery_src() {
56-
return Stream.of(Arguments.of("docker", new HashMap<String, String>()), Arguments.of("docker-swarm", new HashMap<String, String>()), Arguments.of("kubernetes", new HashMap<String, String>()));
55+
return Stream.of(
56+
Arguments.of("docker", Map.of()),
57+
Arguments.of("docker-swarm", Map.of()),
58+
Arguments.of("kubernetes", Map.of())
59+
);
5760
}
5861

5962
@ParameterizedTest
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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.test.proxy;
22+
23+
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.ProxySharingScaler;
24+
import eu.openanalytics.containerproxy.backend.dispatcher.proxysharing.SeatIdKey;
25+
import eu.openanalytics.containerproxy.model.runtime.Proxy;
26+
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.PublicPathKey;
27+
import eu.openanalytics.containerproxy.model.runtime.runtimevalues.TargetIdKey;
28+
import eu.openanalytics.containerproxy.test.helpers.ContainerSetup;
29+
import eu.openanalytics.containerproxy.test.helpers.ShinyProxyInstance;
30+
import eu.openanalytics.containerproxy.util.Retrying;
31+
import org.junit.jupiter.api.Assertions;
32+
import org.junit.jupiter.params.ParameterizedTest;
33+
import org.junit.jupiter.params.provider.Arguments;
34+
import org.junit.jupiter.params.provider.MethodSource;
35+
36+
import java.time.Duration;
37+
import java.time.Instant;
38+
import java.util.Map;
39+
import java.util.stream.Stream;
40+
41+
public class TestPreInitialization {
42+
43+
private static Stream<Arguments> backends() {
44+
return Stream.of(
45+
Arguments.of("docker", Map.of("proxy.container-backend", "docker")),
46+
Arguments.of("docker-swarm", Map.of("proxy.container-backend", "docker-swarm")),
47+
Arguments.of("kubernetes", Map.of("proxy.container-backend", "kubernetes"))
48+
);
49+
}
50+
51+
@ParameterizedTest
52+
@MethodSource("backends")
53+
public void simpleTest(String backend, Map<String, String> properties) {
54+
try (ContainerSetup containerSetup = new ContainerSetup(backend)) {
55+
try (ShinyProxyInstance inst = new ShinyProxyInstance("application-test-pre-initialization.yml", properties, true)) {
56+
ProxySharingScaler proxySharingScaler = inst.getBean("proxySharingScaler_simpleTest", ProxySharingScaler.class);
57+
try {
58+
String id = inst.client.startProxy("simpleTest");
59+
Proxy proxy = inst.proxyService.getProxy(id);
60+
inst.client.testProxyReachable(proxy.getTargetId());
61+
62+
// target id should be different from proxy id
63+
Assertions.assertNotEquals(proxy.getTargetId(), proxy.getId());
64+
Assertions.assertEquals("/api/route/" + proxy.getTargetId() + "/", proxy.getRuntimeValue(PublicPathKey.inst));
65+
Assertions.assertEquals(proxy.getTargetId(), proxy.getRuntimeValue(TargetIdKey.inst));
66+
Assertions.assertNotNull(proxy.getRuntimeValue(SeatIdKey.inst));
67+
68+
waitUntilNoPendingSeats(proxySharingScaler);
69+
Assertions.assertEquals(1, proxySharingScaler.getNumClaimedSeats());
70+
Assertions.assertEquals(1, proxySharingScaler.getNumUnclaimedSeats());
71+
72+
// start an additional app
73+
Instant start = Instant.now();
74+
String id2 = inst.client.startProxy("simpleTest");
75+
Proxy proxy2 = inst.proxyService.getProxy(id2);
76+
inst.client.testProxyReachable(proxy2.getTargetId());
77+
78+
// target id should be different from first app
79+
Assertions.assertNotEquals(proxy2.getTargetId(), proxy.getTargetId());
80+
// seat id should be different from first app
81+
Assertions.assertNotEquals(proxy2.getRuntimeValue(SeatIdKey.inst), proxy.getRuntimeValue(SeatIdKey.inst));
82+
83+
// should have scaled-up
84+
waitUntilNoPendingSeats(proxySharingScaler);
85+
Assertions.assertEquals(2, proxySharingScaler.getNumClaimedSeats());
86+
Assertions.assertEquals(1, proxySharingScaler.getNumUnclaimedSeats());
87+
88+
// stop first app
89+
inst.client.stopProxy(id);
90+
Assertions.assertEquals(0, proxySharingScaler.getNumPendingSeats());
91+
Assertions.assertEquals(1, proxySharingScaler.getNumClaimedSeats());
92+
Assertions.assertEquals(2, proxySharingScaler.getNumUnclaimedSeats());
93+
94+
// wait until scale-down happened
95+
waitUntilUnNumberOfClaimedSeats(proxySharingScaler, 1);
96+
Instant stop = Instant.now();
97+
Assertions.assertEquals(0, proxySharingScaler.getNumPendingSeats());
98+
Assertions.assertEquals(1, proxySharingScaler.getNumClaimedSeats());
99+
Assertions.assertEquals(1, proxySharingScaler.getNumUnclaimedSeats());
100+
// scale-down should take at least two minutes
101+
Assertions.assertTrue(Duration.between(start, stop).toSeconds() > 120);
102+
} finally {
103+
proxySharingScaler.stopAll();
104+
inst.stopAllApps();
105+
}
106+
}
107+
}
108+
}
109+
110+
private void waitUntilNoPendingSeats(ProxySharingScaler proxySharingScaler) {
111+
boolean noPendingSeats = Retrying.retry((c, m) -> {
112+
return proxySharingScaler.getNumPendingSeats() == 0;
113+
}, 60_000, "assert no pending seats", 1, true);
114+
Assertions.assertTrue(noPendingSeats);
115+
}
116+
117+
private void waitUntilUnNumberOfClaimedSeats(ProxySharingScaler proxySharingScaler, int numSeats) {
118+
boolean noPendingSeats = Retrying.retry((c, m) -> {
119+
return proxySharingScaler.getNumUnclaimedSeats() == numSeats;
120+
}, 180_000, "assert number of unclaimed seats", 1, true);
121+
Assertions.assertTrue(noPendingSeats);
122+
}
123+
124+
}
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: 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

0 commit comments

Comments
 (0)