@@ -22,6 +22,7 @@ import (
22
22
"strings"
23
23
"testing"
24
24
25
+ "github.com/containerd/containerd/mount"
25
26
"github.com/containerd/nerdctl/pkg/testutil"
26
27
"gotest.tools/v3/assert"
27
28
)
@@ -248,3 +249,230 @@ func TestRunTmpfs(t *testing.T) {
248
249
// for https://github.com/containerd/nerdctl/issues/594
249
250
base .Cmd ("run" , "--rm" , "--tmpfs" , "/dev/shm:rw,exec,size=1g" , testutil .AlpineImage , "grep" , "/dev/shm" , "/proc/mounts" ).AssertOutWithFunc (f ([]string {"rw" , "nosuid" , "nodev" , "size=1048576k" }, []string {"noexec" }))
250
251
}
252
+
253
+ func TestRunBindMountTmpfs (t * testing.T ) {
254
+ t .Parallel ()
255
+ base := testutil .NewBase (t )
256
+ f := func (allow []string ) func (stdout string ) error {
257
+ return func (stdout string ) error {
258
+ lines := strings .Split (strings .TrimSpace (stdout ), "\n " )
259
+ if len (lines ) != 1 {
260
+ return fmt .Errorf ("expected 1 lines, got %q" , stdout )
261
+ }
262
+ for _ , s := range allow {
263
+ if ! strings .Contains (stdout , s ) {
264
+ return fmt .Errorf ("expected stdout to contain %q, got %q" , s , stdout )
265
+ }
266
+ }
267
+ return nil
268
+ }
269
+ }
270
+ base .Cmd ("run" , "--rm" , "--mount" , "type=tmpfs,target=/tmp" , testutil .AlpineImage , "grep" , "/tmp" , "/proc/mounts" ).AssertOutWithFunc (f ([]string {"rw" , "nosuid" , "nodev" , "noexec" }))
271
+ base .Cmd ("run" , "--rm" , "--mount" , "type=tmpfs,target=/tmp,tmpfs-size=64m" , testutil .AlpineImage , "grep" , "/tmp" , "/proc/mounts" ).AssertOutWithFunc (f ([]string {"rw" , "nosuid" , "nodev" , "size=65536k" }))
272
+ }
273
+
274
+ func TestRunBindMountBind (t * testing.T ) {
275
+ t .Parallel ()
276
+ base := testutil .NewBase (t )
277
+ tID := testutil .Identifier (t )
278
+ rwDir , err := os .MkdirTemp (t .TempDir (), "rw" )
279
+ if err != nil {
280
+ t .Fatal (err )
281
+ }
282
+ roDir , err := os .MkdirTemp (t .TempDir (), "ro" )
283
+ if err != nil {
284
+ t .Fatal (err )
285
+ }
286
+
287
+ containerName := tID
288
+ defer base .Cmd ("rm" , "-f" , containerName ).Run ()
289
+ base .Cmd ("run" ,
290
+ "-d" ,
291
+ "--name" , containerName ,
292
+ "--mount" , fmt .Sprintf ("type=bind,src=%s,target=/mnt1" , rwDir ),
293
+ "--mount" , fmt .Sprintf ("type=bind,src=%s,target=/mnt2,ro" , roDir ),
294
+ testutil .AlpineImage ,
295
+ "top" ,
296
+ ).AssertOK ()
297
+ base .Cmd ("exec" , containerName , "sh" , "-exc" , "echo -n str1 > /mnt1/file1" ).AssertOK ()
298
+ base .Cmd ("exec" , containerName , "sh" , "-exc" , "echo -n str2 > /mnt2/file2" ).AssertFail ()
299
+
300
+ base .Cmd ("run" ,
301
+ "--rm" ,
302
+ "--mount" , fmt .Sprintf ("type=bind,src=%s,target=/mnt1" , rwDir ),
303
+ testutil .AlpineImage ,
304
+ "cat" , "/mnt1/file1" ,
305
+ ).AssertOutExactly ("str1" )
306
+
307
+ // check `bind-propagation`
308
+ f := func (allow string ) func (stdout string ) error {
309
+ return func (stdout string ) error {
310
+ lines := strings .Split (strings .TrimSpace (stdout ), "\n " )
311
+ if len (lines ) != 1 {
312
+ return fmt .Errorf ("expected 1 lines, got %q" , stdout )
313
+ }
314
+ fields := strings .Split (lines [0 ], " " )
315
+ if len (fields ) < 4 {
316
+ return fmt .Errorf ("invalid /proc/mounts format %q" , stdout )
317
+ }
318
+
319
+ options := strings .Split (fields [3 ], "," )
320
+
321
+ found := false
322
+ for _ , s := range options {
323
+ if allow == s {
324
+ found = true
325
+ break
326
+ }
327
+ }
328
+ if ! found {
329
+ return fmt .Errorf ("expected stdout to contain %q, got %+v" , allow , options )
330
+ }
331
+ return nil
332
+ }
333
+ }
334
+ base .Cmd ("exec" , containerName , "grep" , "/mnt1" , "/proc/mounts" ).AssertOutWithFunc (f ("rw" ))
335
+ base .Cmd ("exec" , containerName , "grep" , "/mnt2" , "/proc/mounts" ).AssertOutWithFunc (f ("ro" ))
336
+ }
337
+
338
+ func TestRunBindMountPropagation (t * testing.T ) {
339
+ tID := testutil .Identifier (t )
340
+
341
+ if ! isRootfsShareableMount () {
342
+ t .Skipf ("rootfs doesn't support shared mount, skip test %s" , tID )
343
+ }
344
+
345
+ t .Parallel ()
346
+ base := testutil .NewBase (t )
347
+
348
+ testCases := []struct {
349
+ propagation string
350
+ assertFunc func (containerName , containerNameReplica string )
351
+ }{
352
+ {
353
+ propagation : "rshared" ,
354
+ assertFunc : func (containerName , containerNameReplica string ) {
355
+ // replica can get sub-mounts from original
356
+ base .Cmd ("exec" , containerNameReplica , "cat" , "/mnt1/replica/foo.txt" ).AssertOutExactly ("toreplica" )
357
+
358
+ // and sub-mounts from replica will be propagated to the original too
359
+ base .Cmd ("exec" , containerName , "cat" , "/mnt1/bar/bar.txt" ).AssertOutExactly ("fromreplica" )
360
+ },
361
+ },
362
+ {
363
+ propagation : "rslave" ,
364
+ assertFunc : func (containerName , containerNameReplica string ) {
365
+ // replica can get sub-mounts from original
366
+ base .Cmd ("exec" , containerNameReplica , "cat" , "/mnt1/replica/foo.txt" ).AssertOutExactly ("toreplica" )
367
+
368
+ // but sub-mounts from replica will not be propagated to the original
369
+ base .Cmd ("exec" , containerName , "cat" , "/mnt1/bar/bar.txt" ).AssertFail ()
370
+ },
371
+ },
372
+ {
373
+ propagation : "rprivate" ,
374
+ assertFunc : func (containerName , containerNameReplica string ) {
375
+ // replica can't get sub-mounts from original
376
+ base .Cmd ("exec" , containerNameReplica , "cat" , "/mnt1/replica/foo.txt" ).AssertFail ()
377
+ // and sub-mounts from replica will not be propagated to the original too
378
+ base .Cmd ("exec" , containerName , "cat" , "/mnt1/bar/bar.txt" ).AssertFail ()
379
+ },
380
+ },
381
+ {
382
+ propagation : "" ,
383
+ assertFunc : func (containerName , containerNameReplica string ) {
384
+ // replica can't get sub-mounts from original
385
+ base .Cmd ("exec" , containerNameReplica , "cat" , "/mnt1/replica/foo.txt" ).AssertFail ()
386
+ // and sub-mounts from replica will not be propagated to the original too
387
+ base .Cmd ("exec" , containerName , "cat" , "/mnt1/bar/bar.txt" ).AssertFail ()
388
+ },
389
+ },
390
+ }
391
+
392
+ for _ , tc := range testCases {
393
+ propagationName := tc .propagation
394
+ if propagationName == "" {
395
+ propagationName = "default"
396
+ }
397
+
398
+ t .Logf ("Running test propagation case %s" , propagationName )
399
+
400
+ rwDir , err := os .MkdirTemp (t .TempDir (), "rw" )
401
+ if err != nil {
402
+ t .Fatal (err )
403
+ }
404
+
405
+ containerName := tID + "-" + propagationName
406
+ containerNameReplica := containerName + "-replica"
407
+
408
+ mountOption := fmt .Sprintf ("type=bind,src=%s,target=/mnt1,bind-propagation=%s" , rwDir , tc .propagation )
409
+ if tc .propagation == "" {
410
+ mountOption = fmt .Sprintf ("type=bind,src=%s,target=/mnt1" , rwDir )
411
+ }
412
+
413
+ containers := []struct {
414
+ name string
415
+ mountOption string
416
+ }{
417
+ {
418
+ name : containerName ,
419
+ mountOption : fmt .Sprintf ("type=bind,src=%s,target=/mnt1,bind-propagation=rshared" , rwDir ),
420
+ },
421
+ {
422
+ name : containerNameReplica ,
423
+ mountOption : mountOption ,
424
+ },
425
+ }
426
+ for _ , c := range containers {
427
+ base .Cmd ("run" , "-d" ,
428
+ "--privileged" ,
429
+ "--name" , c .name ,
430
+ "--mount" , c .mountOption ,
431
+ testutil .AlpineImage ,
432
+ "top" ).AssertOK ()
433
+ defer base .Cmd ("rm" , "-f" , c .name ).Run ()
434
+ }
435
+
436
+ // mount in the first container
437
+ base .Cmd ("exec" , containerName , "sh" , "-exc" , "mkdir /app && mkdir /mnt1/replica && mount --bind /app /mnt1/replica && echo -n toreplica > /app/foo.txt" ).AssertOK ()
438
+ base .Cmd ("exec" , containerName , "cat" , "/mnt1/replica/foo.txt" ).AssertOutExactly ("toreplica" )
439
+
440
+ // mount in the second container
441
+ base .Cmd ("exec" , containerNameReplica , "sh" , "-exc" , "mkdir /bar && mkdir /mnt1/bar" ).AssertOK ()
442
+ base .Cmd ("exec" , containerNameReplica , "sh" , "-exc" , "mount --bind /bar /mnt1/bar" ).AssertOK ()
443
+
444
+ base .Cmd ("exec" , containerNameReplica , "sh" , "-exc" , "echo -n fromreplica > /bar/bar.txt" ).AssertOK ()
445
+ base .Cmd ("exec" , containerNameReplica , "cat" , "/mnt1/bar/bar.txt" ).AssertOutExactly ("fromreplica" )
446
+
447
+ // call case specific assert function
448
+ tc .assertFunc (containerName , containerNameReplica )
449
+
450
+ // umount mount point in the first privileged container
451
+ base .Cmd ("exec" , containerNameReplica , "sh" , "-exc" , "umount /mnt1/bar" ).AssertOK ()
452
+ base .Cmd ("exec" , containerName , "sh" , "-exc" , "umount /mnt1/replica" ).AssertOK ()
453
+ }
454
+ }
455
+
456
+ // isRootfsShareableMount will check if /tmp or / support shareable mount
457
+ func isRootfsShareableMount () bool {
458
+ existFunc := func (mi mount.Info ) bool {
459
+ for _ , opt := range strings .Split (mi .Optional , " " ) {
460
+ if strings .HasPrefix (opt , "shared:" ) {
461
+ return true
462
+ }
463
+ }
464
+ return false
465
+ }
466
+
467
+ mi , err := mount .Lookup ("/tmp" )
468
+ if err == nil {
469
+ return existFunc (mi )
470
+ }
471
+
472
+ mi , err = mount .Lookup ("/" )
473
+ if err == nil {
474
+ return existFunc (mi )
475
+ }
476
+
477
+ return false
478
+ }
0 commit comments