Skip to content

Commit 5736b4b

Browse files
Hendrik-HKostyaSha
authored andcommitted
Support volumes propagation flags (Issue 554) (#705)
* Support nocopy option in volume binds (#688) * support volumes propagation flags, fixes #554 * renamed values to match java convention
1 parent b3be4fb commit 5736b4b

File tree

4 files changed

+217
-3
lines changed

4 files changed

+217
-3
lines changed

src/main/java/com/github/dockerjava/api/model/Bind.java

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,48 @@ public class Bind implements Serializable {
1818

1919
private AccessMode accessMode;
2020

21+
/**
22+
* @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_23}
23+
*/
24+
private Boolean noCopy;
25+
2126
/**
2227
* @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_17}
2328
*/
2429
private SELContext secMode;
2530

31+
/**
32+
* @since {@link com.github.dockerjava.core.RemoteApiVersion#VERSION_1_22}
33+
*/
34+
private PropagationMode propagationMode;
35+
2636
public Bind(String path, Volume volume) {
2737
this(path, volume, AccessMode.DEFAULT, SELContext.DEFAULT);
2838
}
2939

40+
public Bind(String path, Volume volume, Boolean noCopy) {
41+
this(path, volume, AccessMode.DEFAULT, SELContext.DEFAULT, noCopy);
42+
}
43+
3044
public Bind(String path, Volume volume, AccessMode accessMode) {
3145
this(path, volume, accessMode, SELContext.DEFAULT);
3246
}
3347

3448
public Bind(String path, Volume volume, AccessMode accessMode, SELContext secMode) {
49+
this(path, volume, accessMode, secMode, null);
50+
}
51+
52+
public Bind(String path, Volume volume, AccessMode accessMode, SELContext secMode, Boolean noCopy) {
53+
this(path, volume, accessMode, secMode, noCopy, PropagationMode.DEFAULT_MODE);
54+
}
55+
56+
public Bind(String path, Volume volume, AccessMode accessMode, SELContext secMode, Boolean noCopy, PropagationMode propagationMode) {
3557
this.path = path;
3658
this.volume = volume;
3759
this.accessMode = accessMode;
3860
this.secMode = secMode;
61+
this.noCopy = noCopy;
62+
this.propagationMode = propagationMode;
3963
}
4064

4165
public String getPath() {
@@ -54,6 +78,14 @@ public SELContext getSecMode() {
5478
return secMode;
5579
}
5680

81+
public Boolean getNoCopy() {
82+
return noCopy;
83+
}
84+
85+
public PropagationMode getPropagationMode() {
86+
return propagationMode;
87+
}
88+
5789
/**
5890
* Parses a bind mount specification to a {@link Bind}.
5991
*
@@ -74,15 +106,25 @@ public static Bind parse(String serialized) {
74106
String[] flags = parts[2].split(",");
75107
AccessMode accessMode = AccessMode.DEFAULT;
76108
SELContext seMode = SELContext.DEFAULT;
109+
Boolean nocopy = null;
110+
PropagationMode propagationMode = PropagationMode.DEFAULT_MODE;
77111
for (String p : flags) {
78112
if (p.length() == 2) {
79113
accessMode = AccessMode.valueOf(p.toLowerCase());
114+
} else if ("nocopy".equals(p)) {
115+
nocopy = true;
116+
} else if (PropagationMode.SHARED.toString().equals(p)) {
117+
propagationMode = PropagationMode.SHARED;
118+
} else if (PropagationMode.SLAVE.toString().equals(p)) {
119+
propagationMode = PropagationMode.SLAVE;
120+
} else if (PropagationMode.PRIVATE.toString().equals(p)) {
121+
propagationMode = PropagationMode.PRIVATE;
80122
} else {
81123
seMode = SELContext.fromString(p);
82124
}
83125
}
84126

85-
return new Bind(parts[0], new Volume(parts[1]), accessMode, seMode);
127+
return new Bind(parts[0], new Volume(parts[1]), accessMode, seMode, nocopy, propagationMode);
86128
}
87129
default: {
88130
throw new IllegalArgumentException();
@@ -102,6 +144,8 @@ public boolean equals(Object obj) {
102144
.append(volume, other.getVolume())
103145
.append(accessMode, other.getAccessMode())
104146
.append(secMode, other.getSecMode())
147+
.append(noCopy, other.getNoCopy())
148+
.append(propagationMode, other.getPropagationMode())
105149
.isEquals();
106150
} else {
107151
return super.equals(obj);
@@ -115,6 +159,8 @@ public int hashCode() {
115159
.append(volume)
116160
.append(accessMode)
117161
.append(secMode)
162+
.append(noCopy)
163+
.append(propagationMode)
118164
.toHashCode();
119165
}
120166

@@ -127,10 +173,12 @@ public int hashCode() {
127173
*/
128174
@Override
129175
public String toString() {
130-
return String.format("%s:%s:%s%s",
176+
return String.format("%s:%s:%s%s%s%s",
131177
path,
132178
volume.getPath(),
133179
accessMode.toString(),
134-
secMode != SELContext.none ? "," + secMode.toString() : "");
180+
secMode != SELContext.none ? "," + secMode.toString() : "",
181+
noCopy != null ? ",nocopy" : "",
182+
propagationMode != PropagationMode.DEFAULT_MODE ? "," + propagationMode.toString() : "");
135183
}
136184
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.github.dockerjava.api.model;
2+
3+
/**
4+
* The propagation mode of a file system or file: <code>shared</code>, <code>slave</code> or <code>private</code>.
5+
*
6+
* @see https://github.com/docker/docker/pull/17034
7+
* @since 1.22
8+
*/
9+
public enum PropagationMode {
10+
/** default */
11+
DEFAULT(""),
12+
13+
/** shared */
14+
SHARED("shared"),
15+
16+
/** slave */
17+
SLAVE("slave"),
18+
19+
/** private */
20+
PRIVATE("private");
21+
22+
/**
23+
* The default {@link PropagationMode}: {@link #DEFAULT}
24+
*/
25+
public static final PropagationMode DEFAULT_MODE = DEFAULT;
26+
27+
private String value;
28+
29+
PropagationMode(String v) {
30+
value = v;
31+
}
32+
33+
@Override
34+
public String toString() {
35+
return value;
36+
}
37+
38+
public static PropagationMode fromString(String v) {
39+
switch (v) {
40+
case "shared":
41+
return SHARED;
42+
case "slave":
43+
return SLAVE;
44+
case "private":
45+
return PRIVATE;
46+
default:
47+
return DEFAULT;
48+
}
49+
}
50+
}

src/test/java/com/github/dockerjava/api/model/BindTest.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static com.github.dockerjava.api.model.AccessMode.ro;
44
import static com.github.dockerjava.api.model.AccessMode.rw;
55
import static org.hamcrest.MatcherAssert.assertThat;
6+
import static org.hamcrest.Matchers.nullValue;
67
import static org.hamcrest.core.Is.is;
78

89
import org.testng.annotations.Test;
@@ -16,6 +17,8 @@ public void parseUsingDefaultAccessMode() {
1617
assertThat(bind.getVolume().getPath(), is("/container"));
1718
assertThat(bind.getAccessMode(), is(AccessMode.DEFAULT));
1819
assertThat(bind.getSecMode(), is(SELContext.none));
20+
assertThat(bind.getNoCopy(), nullValue());
21+
assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
1922
}
2023

2124
@Test
@@ -25,6 +28,52 @@ public void parseReadWrite() {
2528
assertThat(bind.getVolume().getPath(), is("/container"));
2629
assertThat(bind.getAccessMode(), is(rw));
2730
assertThat(bind.getSecMode(), is(SELContext.none));
31+
assertThat(bind.getNoCopy(), nullValue());
32+
assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
33+
}
34+
35+
@Test
36+
public void parseReadWriteNoCopy() {
37+
Bind bind = Bind.parse("/host:/container:rw,nocopy");
38+
assertThat(bind.getPath(), is("/host"));
39+
assertThat(bind.getVolume().getPath(), is("/container"));
40+
assertThat(bind.getAccessMode(), is(rw));
41+
assertThat(bind.getSecMode(), is(SELContext.none));
42+
assertThat(bind.getNoCopy(), is(true));
43+
assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
44+
}
45+
46+
@Test
47+
public void parseReadWriteShared() {
48+
Bind bind = Bind.parse("/host:/container:rw,shared");
49+
assertThat(bind.getPath(), is("/host"));
50+
assertThat(bind.getVolume().getPath(), is("/container"));
51+
assertThat(bind.getAccessMode(), is(rw));
52+
assertThat(bind.getSecMode(), is(SELContext.none));
53+
assertThat(bind.getNoCopy(), nullValue());
54+
assertThat(bind.getPropagationMode(), is(PropagationMode.SHARED));
55+
}
56+
57+
@Test
58+
public void parseReadWriteSlave() {
59+
Bind bind = Bind.parse("/host:/container:rw,slave");
60+
assertThat(bind.getPath(), is("/host"));
61+
assertThat(bind.getVolume().getPath(), is("/container"));
62+
assertThat(bind.getAccessMode(), is(rw));
63+
assertThat(bind.getSecMode(), is(SELContext.none));
64+
assertThat(bind.getNoCopy(), nullValue());
65+
assertThat(bind.getPropagationMode(), is(PropagationMode.SLAVE));
66+
}
67+
68+
@Test
69+
public void parseReadWritePrivate() {
70+
Bind bind = Bind.parse("/host:/container:rw,private");
71+
assertThat(bind.getPath(), is("/host"));
72+
assertThat(bind.getVolume().getPath(), is("/container"));
73+
assertThat(bind.getAccessMode(), is(rw));
74+
assertThat(bind.getSecMode(), is(SELContext.none));
75+
assertThat(bind.getNoCopy(), nullValue());
76+
assertThat(bind.getPropagationMode(), is(PropagationMode.PRIVATE));
2877
}
2978

3079
@Test
@@ -34,6 +83,8 @@ public void parseReadOnly() {
3483
assertThat(bind.getVolume().getPath(), is("/container"));
3584
assertThat(bind.getAccessMode(), is(ro));
3685
assertThat(bind.getSecMode(), is(SELContext.none));
86+
assertThat(bind.getNoCopy(), nullValue());
87+
assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
3788
}
3889

3990
@Test
@@ -43,12 +94,16 @@ public void parseSELOnly() {
4394
assertThat(bind.getVolume().getPath(), is("/container"));
4495
assertThat(bind.getAccessMode(), is(AccessMode.DEFAULT));
4596
assertThat(bind.getSecMode(), is(SELContext.single));
97+
assertThat(bind.getNoCopy(), nullValue());
98+
assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
4699

47100
bind = Bind.parse("/host:/container:z");
48101
assertThat(bind.getPath(), is("/host"));
49102
assertThat(bind.getVolume().getPath(), is("/container"));
50103
assertThat(bind.getAccessMode(), is(AccessMode.DEFAULT));
51104
assertThat(bind.getSecMode(), is(SELContext.shared));
105+
assertThat(bind.getNoCopy(), nullValue());
106+
assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
52107
}
53108

54109
@Test
@@ -58,6 +113,8 @@ public void parseReadWriteSEL() {
58113
assertThat(bind.getVolume().getPath(), is("/container"));
59114
assertThat(bind.getAccessMode(), is(rw));
60115
assertThat(bind.getSecMode(), is(SELContext.single));
116+
assertThat(bind.getNoCopy(), nullValue());
117+
assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
61118
}
62119

63120
@Test
@@ -67,6 +124,8 @@ public void parseReadOnlySEL() {
67124
assertThat(bind.getVolume().getPath(), is("/container"));
68125
assertThat(bind.getAccessMode(), is(ro));
69126
assertThat(bind.getSecMode(), is(SELContext.shared));
127+
assertThat(bind.getNoCopy(), nullValue());
128+
assertThat(bind.getPropagationMode(), is(PropagationMode.DEFAULT_MODE));
70129
}
71130

72131
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Error parsing Bind.*")
@@ -94,6 +153,26 @@ public void toStringReadWrite() {
94153
assertThat(Bind.parse("/host:/container:rw").toString(), is("/host:/container:rw"));
95154
}
96155

156+
@Test
157+
public void toStringReadWriteNoCopy() {
158+
assertThat(Bind.parse("/host:/container:rw,nocopy").toString(), is("/host:/container:rw,nocopy"));
159+
}
160+
161+
@Test
162+
public void toStringReadWriteShared() {
163+
assertThat(Bind.parse("/host:/container:rw,shared").toString(), is("/host:/container:rw,shared"));
164+
}
165+
166+
@Test
167+
public void toStringReadWriteSlave() {
168+
assertThat(Bind.parse("/host:/container:rw,slave").toString(), is("/host:/container:rw,slave"));
169+
}
170+
171+
@Test
172+
public void toStringReadWritePrivate() {
173+
assertThat(Bind.parse("/host:/container:rw,private").toString(), is("/host:/container:rw,private"));
174+
}
175+
97176
@Test
98177
public void toStringDefaultAccessMode() {
99178
assertThat(Bind.parse("/host:/container").toString(), is("/host:/container:rw"));

src/test/java/com/github/dockerjava/netty/exec/CreateContainerCmdExecTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.dockerjava.api.command.CreateContainerResponse;
44
import com.github.dockerjava.api.command.CreateNetworkResponse;
5+
import com.github.dockerjava.api.command.CreateVolumeResponse;
56
import com.github.dockerjava.api.command.InspectContainerResponse;
67
import com.github.dockerjava.api.exception.ConflictException;
78
import com.github.dockerjava.api.exception.DockerException;
@@ -15,6 +16,7 @@
1516
import com.github.dockerjava.api.model.Network;
1617
import com.github.dockerjava.api.model.Ports;
1718
import com.github.dockerjava.api.model.Ports.Binding;
19+
import com.github.dockerjava.core.RemoteApiVersion;
1820
import com.github.dockerjava.api.model.RestartPolicy;
1921
import com.github.dockerjava.api.model.Ulimit;
2022
import com.github.dockerjava.api.model.Volume;
@@ -23,6 +25,7 @@
2325

2426
import org.apache.commons.io.FileUtils;
2527
import org.testng.ITestResult;
28+
import org.testng.SkipException;
2629
import org.testng.annotations.AfterMethod;
2730
import org.testng.annotations.AfterTest;
2831
import org.testng.annotations.BeforeMethod;
@@ -40,6 +43,7 @@
4043

4144
import static com.github.dockerjava.api.model.Capability.MKNOD;
4245
import static com.github.dockerjava.api.model.Capability.NET_ADMIN;
46+
import static com.github.dockerjava.utils.TestUtils.getVersion;
4347
import static org.hamcrest.MatcherAssert.assertThat;
4448
import static org.hamcrest.Matchers.contains;
4549
import static org.hamcrest.Matchers.containsInAnyOrder;
@@ -138,6 +142,39 @@ public void createContainerWithReadOnlyVolume() throws DockerException {
138142
// assertFalse(inspectContainerResponse.getMounts().get(0).getRW());
139143
}
140144

145+
@Test
146+
public void createContainerWithNoCopyVolumes() throws DockerException {
147+
final RemoteApiVersion apiVersion = getVersion(dockerClient);
148+
149+
if (!apiVersion.isGreaterOrEqual(RemoteApiVersion.VERSION_1_23)) {
150+
throw new SkipException("API version should be >= 1.23");
151+
}
152+
153+
Volume volume1 = new Volume("/opt/webapp1");
154+
String container1Name = UUID.randomUUID().toString();
155+
156+
CreateVolumeResponse volumeResponse = dockerClient.createVolumeCmd().withName("webapp1").exec();
157+
assertThat(volumeResponse.getName(), equalTo("webapp1"));
158+
assertThat(volumeResponse.getDriver(), equalTo("local"));
159+
assertThat(volumeResponse.getMountpoint(), containsString("/webapp1/"));
160+
161+
Bind bind1 = new Bind("webapp1", volume1, true);
162+
163+
CreateContainerResponse container1 = dockerClient.createContainerCmd("busybox").withCmd("sleep", "9999")
164+
.withName(container1Name)
165+
.withBinds(bind1).exec();
166+
LOG.info("Created container1 {}", container1.toString());
167+
168+
InspectContainerResponse inspectContainerResponse1 = dockerClient.inspectContainerCmd(container1.getId()).exec();
169+
170+
assertThat(Arrays.asList(inspectContainerResponse1.getHostConfig().getBinds()), contains(bind1));
171+
assertThat(inspectContainerResponse1, mountedVolumes(contains(volume1)));
172+
173+
assertThat(inspectContainerResponse1.getMounts().get(0).getDestination(), equalTo(volume1));
174+
assertThat(inspectContainerResponse1.getMounts().get(0).getMode(), equalTo("rw,nocopy"));
175+
assertThat(inspectContainerResponse1.getMounts().get(0).getRW(), equalTo(true));
176+
}
177+
141178
@Test
142179
public void createContainerWithVolumesFrom() throws DockerException {
143180

0 commit comments

Comments
 (0)