Skip to content

Commit d407af9

Browse files
committed
Fix #34842: allow "i" in memory of Docker containers
1 parent 7c11c3d commit d407af9

3 files changed

Lines changed: 171 additions & 7 deletions

File tree

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,19 +171,25 @@ protected String getProperty(String key, String defaultValue) {
171171
protected abstract String getPropertyPrefix();
172172

173173
protected Long memoryToBytes(String memory) {
174-
if (memory == null || memory.isEmpty()) return null;
175-
Matcher matcher = Pattern.compile("(\\d+)([bkmg]?)").matcher(memory.toLowerCase());
176-
if (!matcher.matches()) throw new IllegalArgumentException("Invalid memory argument: " + memory);
177-
long mem = Long.parseLong(matcher.group(1));
174+
if (memory == null || memory.isEmpty()) {
175+
return null;
176+
}
177+
if (memory.contains(",")) {
178+
throw new IllegalArgumentException("Invalid memory argument: " + memory + ", no ',' allowed in number");
179+
}
180+
Matcher matcher = Pattern.compile("(\\d+\\.?\\d*)([bkmg]?)i?").matcher(memory.toLowerCase());
181+
if (!matcher.matches()) {
182+
throw new IllegalArgumentException("Invalid memory argument: " + memory);
183+
}
184+
Double mem = Double.parseDouble(matcher.group(1));
178185
String unit = matcher.group(2);
179186
switch (unit) {
180187
case "k" -> mem *= 1024;
181188
case "m" -> mem *= 1024 * 1024;
182189
case "g" -> mem *= 1024 * 1024 * 1024;
183-
default -> {
184-
}
190+
default -> throw new IllegalArgumentException("Invalid memory argument: " + memory);
185191
}
186-
return mem;
192+
return mem.longValue();
187193
}
188194

189195
protected Map<String, String> buildEnv(Authentication user, ContainerSpec containerSpec, Proxy proxy) throws IOException {
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2025 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 com.google.common.base.Throwables;
24+
import eu.openanalytics.containerproxy.ContainerProxyException;
25+
import eu.openanalytics.containerproxy.model.runtime.Proxy;
26+
import eu.openanalytics.containerproxy.model.spec.ProxySpec;
27+
import eu.openanalytics.containerproxy.service.InvalidParametersException;
28+
import eu.openanalytics.containerproxy.test.helpers.ContainerSetup;
29+
import eu.openanalytics.containerproxy.test.helpers.ShinyProxyInstance;
30+
import org.junit.jupiter.api.AfterAll;
31+
import org.junit.jupiter.api.Assertions;
32+
import org.junit.jupiter.api.Test;
33+
import org.mandas.docker.client.DefaultDockerClient;
34+
import org.mandas.docker.client.DockerClient;
35+
import org.mandas.docker.client.builder.jersey.JerseyDockerClientBuilder;
36+
import org.mandas.docker.client.exceptions.DockerCertificateException;
37+
import org.mandas.docker.client.exceptions.DockerException;
38+
import org.mandas.docker.client.messages.Container;
39+
import org.mandas.docker.client.messages.ContainerInfo;
40+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
41+
import org.springframework.security.core.Authentication;
42+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
43+
44+
import java.util.HashMap;
45+
import java.util.List;
46+
import java.util.Map;
47+
import java.util.UUID;
48+
49+
public class TestIntegrationOnDocker {
50+
51+
private static final ShinyProxyInstance inst = new ShinyProxyInstance("application-test-docker.yml", new HashMap<>());
52+
53+
@AfterAll
54+
public static void afterAll() {
55+
inst.close();
56+
}
57+
58+
@Test
59+
public void testMemorySpecification() throws DockerCertificateException, DockerException, InterruptedException, InvalidParametersException {
60+
try (ContainerSetup containerSetup = new ContainerSetup("docker")) {
61+
try (DefaultDockerClient dockerClient = new JerseyDockerClientBuilder().fromEnv().build()) {
62+
String id = inst.client.startProxy("01_hello_memory1");
63+
Proxy proxy = inst.proxyService.getProxy(id);
64+
65+
List<Container> containers = dockerClient.listContainers(DockerClient.ListContainersParam.withStatusRunning(), DockerClient.ListContainersParam.withLabel("openanalytics.eu/sp-proxied-app"));
66+
Container container = containers.getFirst();
67+
Assertions.assertEquals(1, containers.size());
68+
Assertions.assertEquals("openanalytics/shinyproxy-integration-test-app", container.image());
69+
ContainerInfo containerInfo = dockerClient.inspectContainer(container.id());
70+
Assertions.assertEquals(268435456, containerInfo.hostConfig().memoryReservation());
71+
Assertions.assertEquals(1073741824, containerInfo.hostConfig().memory());
72+
73+
inst.proxyService.stopProxy(null, proxy, true).run();
74+
}
75+
}
76+
}
77+
78+
@Test
79+
public void testInvalidMemorySpecification1() throws DockerCertificateException, DockerException, InterruptedException, InvalidParametersException {
80+
try (ContainerSetup containerSetup = new ContainerSetup("docker")) {
81+
try (DefaultDockerClient dockerClient = new JerseyDockerClientBuilder().fromEnv().build()) {
82+
Authentication auth = UsernamePasswordAuthenticationToken.authenticated("demo", null, List.of(new SimpleGrantedAuthority("ROLE_GROUP1")));
83+
ProxySpec spec = inst.specProvider.getSpec("01_hello_memory2");
84+
ContainerProxyException ex = Assertions.assertThrows(ContainerProxyException.class, () -> {
85+
inst.proxyService.startProxy(auth, spec, null, UUID.randomUUID().toString(), Map.of()).run();
86+
});
87+
Assertions.assertEquals("Invalid memory argument: 256Mik", Throwables.getRootCause(ex).getMessage());
88+
}
89+
}
90+
}
91+
92+
@Test
93+
public void testDecimalMemorySpecification() throws DockerCertificateException, DockerException, InterruptedException, InvalidParametersException {
94+
try (ContainerSetup containerSetup = new ContainerSetup("docker")) {
95+
try (DefaultDockerClient dockerClient = new JerseyDockerClientBuilder().fromEnv().build()) {
96+
String id = inst.client.startProxy("01_hello_memory3");
97+
Proxy proxy = inst.proxyService.getProxy(id);
98+
99+
List<Container> containers = dockerClient.listContainers(DockerClient.ListContainersParam.withStatusRunning(), DockerClient.ListContainersParam.withLabel("openanalytics.eu/sp-proxied-app"));
100+
Container container = containers.getFirst();
101+
Assertions.assertEquals(1, containers.size());
102+
Assertions.assertEquals("openanalytics/shinyproxy-integration-test-app", container.image());
103+
ContainerInfo containerInfo = dockerClient.inspectContainer(container.id());
104+
Assertions.assertEquals(536870912, containerInfo.hostConfig().memoryReservation());
105+
106+
inst.proxyService.stopProxy(null, proxy, true).run();
107+
}
108+
}
109+
}
110+
111+
@Test
112+
public void testInvalidMemorySpecification2() throws DockerCertificateException, DockerException, InterruptedException, InvalidParametersException {
113+
try (ContainerSetup containerSetup = new ContainerSetup("docker")) {
114+
try (DefaultDockerClient dockerClient = new JerseyDockerClientBuilder().fromEnv().build()) {
115+
Authentication auth = UsernamePasswordAuthenticationToken.authenticated("demo", null, List.of(new SimpleGrantedAuthority("ROLE_GROUP1")));
116+
ProxySpec spec = inst.specProvider.getSpec("01_hello_memory4");
117+
ContainerProxyException ex = Assertions.assertThrows(ContainerProxyException.class, () -> {
118+
inst.proxyService.startProxy(auth, spec, null, UUID.randomUUID().toString(), Map.of()).run();
119+
});
120+
Assertions.assertEquals("Invalid memory argument: 0,5Gi, no ',' allowed in number", Throwables.getRootCause(ex).getMessage());
121+
}
122+
}
123+
}
124+
125+
}

src/test/resources/application-test-docker.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,36 @@ proxy:
3737
port-mapping:
3838
- name: default
3939
port: 3838
40+
- id: 01_hello_memory1
41+
container-specs:
42+
- image: "openanalytics/shinyproxy-integration-test-app"
43+
cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ]
44+
port-mapping:
45+
- name: default
46+
port: 3838
47+
memory-request: 256Mi
48+
memory-limit: 1G
49+
- id: 01_hello_memory2
50+
container-specs:
51+
- image: "openanalytics/shinyproxy-integration-test-app"
52+
cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ]
53+
port-mapping:
54+
- name: default
55+
port: 3838
56+
memory-request: 256Mik
57+
- id: 01_hello_memory3
58+
container-specs:
59+
- image: "openanalytics/shinyproxy-integration-test-app"
60+
cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ]
61+
port-mapping:
62+
- name: default
63+
port: 3838
64+
memory-request: 0.5Gi
65+
- id: 01_hello_memory4
66+
container-specs:
67+
- image: "openanalytics/shinyproxy-integration-test-app"
68+
cmd: [ "R", "-e", "shinyproxy::run_01_hello()" ]
69+
port-mapping:
70+
- name: default
71+
port: 3838
72+
memory-request: 0,5Gi

0 commit comments

Comments
 (0)