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