mounter_linux_test.go 5.7 KB

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