github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/pkg/mount/mounter_linux_test.go (about)

     1  // +build linux
     2  
     3  package mount
     4  
     5  import (
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"strings"
    10  	"testing"
    11  
    12  	selinux "github.com/opencontainers/selinux/go-selinux"
    13  )
    14  
    15  func TestMount(t *testing.T) {
    16  	if os.Getuid() != 0 {
    17  		t.Skip("not root tests would fail")
    18  	}
    19  
    20  	source, err := ioutil.TempDir("", "mount-test-source-")
    21  	if err != nil {
    22  		t.Fatal(err)
    23  	}
    24  	defer os.RemoveAll(source)
    25  
    26  	// Ensure we have a known start point by mounting tmpfs with given options
    27  	if err := Mount("tmpfs", source, "tmpfs", "private"); err != nil {
    28  		t.Fatal(err)
    29  	}
    30  	defer ensureUnmount(t, source)
    31  	validateMount(t, source, "", "", "")
    32  	if t.Failed() {
    33  		t.FailNow()
    34  	}
    35  
    36  	target, err := ioutil.TempDir("", "mount-test-target-")
    37  	if err != nil {
    38  		t.Fatal(err)
    39  	}
    40  	defer os.RemoveAll(target)
    41  
    42  	tests := []struct {
    43  		source           string
    44  		ftype            string
    45  		options          string
    46  		expectedOpts     string
    47  		expectedOptional string
    48  		expectedVFS      string
    49  	}{
    50  		// No options
    51  		{"tmpfs", "tmpfs", "", "", "", ""},
    52  		// Default rw / ro test
    53  		{source, "", "bind", "", "", ""},
    54  		{source, "", "bind,private", "", "", ""},
    55  		{source, "", "bind,shared", "", "shared", ""},
    56  		{source, "", "bind,slave", "", "master", ""},
    57  		{source, "", "bind,unbindable", "", "unbindable", ""},
    58  		// Read Write tests
    59  		{source, "", "bind,rw", "rw", "", ""},
    60  		{source, "", "bind,rw,private", "rw", "", ""},
    61  		{source, "", "bind,rw,shared", "rw", "shared", ""},
    62  		{source, "", "bind,rw,slave", "rw", "master", ""},
    63  		{source, "", "bind,rw,unbindable", "rw", "unbindable", ""},
    64  		// Read Only tests
    65  		{source, "", "bind,ro", "ro", "", ""},
    66  		{source, "", "bind,ro,private", "ro", "", ""},
    67  		{source, "", "bind,ro,shared", "ro", "shared", ""},
    68  		{source, "", "bind,ro,slave", "ro", "master", ""},
    69  		{source, "", "bind,ro,unbindable", "ro", "unbindable", ""},
    70  		// Remount tests to change per filesystem options
    71  		{"", "", "remount,size=128k", "rw", "", "rw,size=128k"},
    72  		{"", "", "remount,ro,size=128k", "ro", "", "ro,size=128k"},
    73  	}
    74  
    75  	for _, tc := range tests {
    76  		ftype, options := tc.ftype, tc.options
    77  		if tc.ftype == "" {
    78  			ftype = "none"
    79  		}
    80  		if tc.options == "" {
    81  			options = "none"
    82  		}
    83  
    84  		t.Run(fmt.Sprintf("%v-%v", ftype, options), func(t *testing.T) {
    85  			if strings.Contains(tc.options, "slave") {
    86  				// Slave requires a shared source
    87  				if err := MakeShared(source); err != nil {
    88  					t.Fatal(err)
    89  				}
    90  				defer func() {
    91  					if err := MakePrivate(source); err != nil {
    92  						t.Fatal(err)
    93  					}
    94  				}()
    95  			}
    96  			if strings.Contains(tc.options, "remount") {
    97  				// create a new mount to remount first
    98  				if err := Mount("tmpfs", target, "tmpfs", ""); err != nil {
    99  					t.Fatal(err)
   100  				}
   101  			}
   102  			if err := Mount(tc.source, target, tc.ftype, tc.options); err != nil {
   103  				t.Fatal(err)
   104  			}
   105  			defer ensureUnmount(t, target)
   106  			expectedVFS := tc.expectedVFS
   107  			if selinux.GetEnabled() && expectedVFS != "" {
   108  				expectedVFS = expectedVFS + ",seclabel"
   109  			}
   110  			validateMount(t, target, tc.expectedOpts, tc.expectedOptional, expectedVFS)
   111  		})
   112  	}
   113  }
   114  
   115  // ensureUnmount umounts mnt checking for errors
   116  func ensureUnmount(t *testing.T, mnt string) {
   117  	if err := Unmount(mnt); err != nil {
   118  		t.Error(err)
   119  	}
   120  }
   121  
   122  // validateMount checks that mnt has the given options
   123  func validateMount(t *testing.T, mnt string, opts, optional, vfs string) {
   124  	info, err := GetMounts()
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  
   129  	wantedOpts := make(map[string]struct{})
   130  	if opts != "" {
   131  		for _, opt := range strings.Split(opts, ",") {
   132  			wantedOpts[opt] = struct{}{}
   133  		}
   134  	}
   135  
   136  	wantedOptional := make(map[string]struct{})
   137  	if optional != "" {
   138  		for _, opt := range strings.Split(optional, ",") {
   139  			wantedOptional[opt] = struct{}{}
   140  		}
   141  	}
   142  
   143  	wantedVFS := make(map[string]struct{})
   144  	if vfs != "" {
   145  		for _, opt := range strings.Split(vfs, ",") {
   146  			wantedVFS[opt] = struct{}{}
   147  		}
   148  	}
   149  
   150  	mnts := make(map[int]*Info, len(info))
   151  	for _, mi := range info {
   152  		mnts[mi.ID] = mi
   153  	}
   154  
   155  	for _, mi := range info {
   156  		if mi.Mountpoint != mnt {
   157  			continue
   158  		}
   159  
   160  		// Use parent info as the defaults
   161  		p := mnts[mi.Parent]
   162  		pOpts := make(map[string]struct{})
   163  		if p.Opts != "" {
   164  			for _, opt := range strings.Split(p.Opts, ",") {
   165  				pOpts[clean(opt)] = struct{}{}
   166  			}
   167  		}
   168  		pOptional := make(map[string]struct{})
   169  		if p.Optional != "" {
   170  			for _, field := range strings.Split(p.Optional, ",") {
   171  				pOptional[clean(field)] = struct{}{}
   172  			}
   173  		}
   174  
   175  		// Validate Opts
   176  		if mi.Opts != "" {
   177  			for _, opt := range strings.Split(mi.Opts, ",") {
   178  				opt = clean(opt)
   179  				if !has(wantedOpts, opt) && !has(pOpts, opt) {
   180  					t.Errorf("unexpected mount option %q expected %q", opt, opts)
   181  				}
   182  				delete(wantedOpts, opt)
   183  			}
   184  		}
   185  		for opt := range wantedOpts {
   186  			t.Errorf("missing mount option %q found %q", opt, mi.Opts)
   187  		}
   188  
   189  		// Validate Optional
   190  		if mi.Optional != "" {
   191  			for _, field := range strings.Split(mi.Optional, ",") {
   192  				field = clean(field)
   193  				if !has(wantedOptional, field) && !has(pOptional, field) {
   194  					t.Errorf("unexpected optional failed %q expected %q", field, optional)
   195  				}
   196  				delete(wantedOptional, field)
   197  			}
   198  		}
   199  		for field := range wantedOptional {
   200  			t.Errorf("missing optional field %q found %q", field, mi.Optional)
   201  		}
   202  
   203  		// Validate VFS if set
   204  		if vfs != "" {
   205  			if mi.VfsOpts != "" {
   206  				for _, opt := range strings.Split(mi.VfsOpts, ",") {
   207  					opt = clean(opt)
   208  					if !has(wantedVFS, opt) {
   209  						t.Errorf("unexpected mount option %q expected %q", opt, vfs)
   210  					}
   211  					delete(wantedVFS, opt)
   212  				}
   213  			}
   214  			for opt := range wantedVFS {
   215  				t.Errorf("missing mount option %q found %q", opt, mi.VfsOpts)
   216  			}
   217  		}
   218  
   219  		return
   220  	}
   221  
   222  	t.Errorf("failed to find mount %q", mnt)
   223  }
   224  
   225  // clean strips off any value param after the colon
   226  func clean(v string) string {
   227  	return strings.SplitN(v, ":", 2)[0]
   228  }
   229  
   230  // has returns true if key is a member of m
   231  func has(m map[string]struct{}, key string) bool {
   232  	_, ok := m[key]
   233  	return ok
   234  }