@@ -55,23 +55,22 @@ public class TestJcmd {
55
55
private static final String IMAGE_NAME = Common .imageName ("jcmd" );
56
56
private static final int TIME_TO_RUN_CONTAINER_PROCESS = (int ) (10 * Utils .TIMEOUT_FACTOR ); // seconds
57
57
private static final String CONTAINER_NAME = "test-container" ;
58
+ private static final boolean IS_PODMAN = Container .ENGINE_COMMAND .contains ("podman" );
59
+ private static final String ROOT_UID = "0" ;
58
60
59
61
60
62
public static void main (String [] args ) throws Exception {
61
63
DockerTestUtils .canTestDocker ();
62
64
63
- // See JDK-8273216 for details
64
- if (Container .ENGINE_COMMAND .equals ("podman" )) {
65
- throw new SkippedException ("JCMD does not work across container boundaries when using Podman" );
65
+ // podman versions below 3.3.1 hava a bug where cross-container testing with correct
66
+ // permissions fails. See JDK-8273216
67
+ if (IS_PODMAN && PodmanVersion .VERSION_3_3_1 .compareTo (getPodmanVersion ()) > 0 ) {
68
+ throw new SkippedException ("Podman version too old for this test. Expected >= 3.3.1" );
66
69
}
67
70
68
71
// Need to create a custom dockerfile where user name and id, as well as group name and id
69
72
// of the JVM running in container must match the ones from the inspecting JCMD process.
70
- String uid = getId ("-u" );
71
- String gid = getId ("-g" );
72
- String userName = getId ("-un" );
73
- String groupName = getId ("-gn" );
74
- String content = generateCustomDockerfile (uid , gid , userName , groupName );
73
+ String content = generateCustomDockerfile ();
75
74
DockerTestUtils .buildJdkContainerImage (IMAGE_NAME , content );
76
75
77
76
try {
@@ -135,17 +134,26 @@ private static void testVmInfo(long pid) throws Exception {
135
134
136
135
// Need to make sure that user name+id and group name+id are created for the image, and
137
136
// match the host system. This is necessary to allow proper permission/access for JCMD.
138
- private static String generateCustomDockerfile ( String uid , String gid ,
139
- String userName , String groupName ) throws Exception {
137
+ // For podman --userns=keep-id is sufficient.
138
+ private static String generateCustomDockerfile ( ) throws Exception {
140
139
StringBuilder sb = new StringBuilder ();
141
140
sb .append (String .format ("FROM %s:%s\n " , DockerfileConfig .getBaseImageName (),
142
141
DockerfileConfig .getBaseImageVersion ()));
143
142
sb .append ("COPY /jdk /jdk\n " );
144
143
sb .append ("ENV JAVA_HOME=/jdk\n " );
145
144
146
- sb .append (String .format ("RUN groupadd --gid %s %s \n " , gid , groupName ));
147
- sb .append (String .format ("RUN useradd --uid %s --gid %s %s \n " , uid , gid , userName ));
148
- sb .append (String .format ("USER %s \n " , userName ));
145
+ if (!IS_PODMAN ) { // only needed for docker
146
+ String uid = getId ("-u" );
147
+ String gid = getId ("-g" );
148
+ String userName = getId ("-un" );
149
+ String groupName = getId ("-gn" );
150
+ // Only needed when run as regular user. UID == 0 should already exist
151
+ if (!ROOT_UID .equals (uid )) {
152
+ sb .append (String .format ("RUN groupadd --gid %s %s \n " , gid , groupName ));
153
+ sb .append (String .format ("RUN useradd --uid %s --gid %s %s \n " , uid , gid , userName ));
154
+ sb .append (String .format ("USER %s \n " , userName ));
155
+ }
156
+ }
149
157
150
158
sb .append ("CMD [\" /bin/bash\" ]\n " );
151
159
@@ -155,12 +163,17 @@ private static String generateCustomDockerfile(String uid, String gid,
155
163
156
164
private static Process startObservedContainer () throws Exception {
157
165
DockerRunOptions opts = new DockerRunOptions (IMAGE_NAME , "/jdk/bin/java" , "EventGeneratorLoop" );
158
- opts .addDockerOpts ("--volume" , Utils .TEST_CLASSES + ":/test-classes/" )
166
+ opts .addDockerOpts ("--volume" , Utils .TEST_CLASSES + ":/test-classes/:z " )
159
167
.addJavaOpts ("-cp" , "/test-classes/" )
160
168
.addDockerOpts ("--cap-add=SYS_PTRACE" )
161
169
.addDockerOpts ("--name" , CONTAINER_NAME )
162
170
.addClassOptions ("" + TIME_TO_RUN_CONTAINER_PROCESS );
163
171
172
+ if (IS_PODMAN ) {
173
+ // map the current userid to the one in the target namespace
174
+ opts .addDockerOpts ("--userns=keep-id" );
175
+ }
176
+
164
177
// avoid large Xmx
165
178
opts .appendTestJavaOptions = false ;
166
179
@@ -190,4 +203,78 @@ private static String getId(String param) throws Exception {
190
203
System .out .println ("getId() " + param + " returning: " + result );
191
204
return result ;
192
205
}
206
+
207
+ // pre: IS_PODMAN == true
208
+ private static String getPodmanVersionStr () {
209
+ if (!IS_PODMAN ) {
210
+ return null ;
211
+ }
212
+ try {
213
+ ProcessBuilder pb = new ProcessBuilder (Container .ENGINE_COMMAND , "--version" );
214
+ OutputAnalyzer out = new OutputAnalyzer (pb .start ())
215
+ .shouldHaveExitValue (0 );
216
+ String result = out .asLines ().get (0 );
217
+ System .out .println (Container .ENGINE_COMMAND + " --version returning: " + result );
218
+ return result ;
219
+ } catch (Exception e ) {
220
+ System .out .println (Container .ENGINE_COMMAND + " --version command failed. Returning null" );
221
+ return null ;
222
+ }
223
+ }
224
+
225
+ private static PodmanVersion getPodmanVersion () {
226
+ return PodmanVersion .fromVersionString (getPodmanVersionStr ());
227
+ }
228
+
229
+ private static class PodmanVersion implements Comparable <PodmanVersion > {
230
+ private static final PodmanVersion DEFAULT = new PodmanVersion (0 , 0 , 0 );
231
+ private static final PodmanVersion VERSION_3_3_1 = new PodmanVersion (3 , 3 , 1 );
232
+ private final int major ;
233
+ private final int minor ;
234
+ private final int micro ;
235
+
236
+ private PodmanVersion (int major , int minor , int micro ) {
237
+ this .major = major ;
238
+ this .minor = minor ;
239
+ this .micro = micro ;
240
+ }
241
+
242
+ @ Override
243
+ public int compareTo (PodmanVersion other ) {
244
+ if (this .major > other .major ) {
245
+ return 1 ;
246
+ } else if (this .major < other .major ) {
247
+ return -1 ;
248
+ } else { // equal major
249
+ if (this .minor > other .minor ) {
250
+ return 1 ;
251
+ } else if (this .minor < other .minor ) {
252
+ return -1 ;
253
+ } else { // equal majors and minors
254
+ if (this .micro > other .micro ) {
255
+ return 1 ;
256
+ } else if (this .micro < other .micro ) {
257
+ return -1 ;
258
+ } else {
259
+ // equal majors, minors, micro
260
+ return 0 ;
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ private static PodmanVersion fromVersionString (String version ) {
267
+ try {
268
+ // Example 'podman version 3.2.1'
269
+ String versNums = version .split ("\\ s+" , 3 )[2 ];
270
+ String [] numbers = versNums .split ("\\ ." , 3 );
271
+ return new PodmanVersion (Integer .parseInt (numbers [0 ]),
272
+ Integer .parseInt (numbers [1 ]),
273
+ Integer .parseInt (numbers [2 ]));
274
+ } catch (Exception e ) {
275
+ System .out .println ("Failed to parse podman version: " + version );
276
+ return DEFAULT ;
277
+ }
278
+ }
279
+ }
193
280
}
0 commit comments