local_linux_test.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. //go:build linux
  2. package local // import "github.com/docker/docker/volume/local"
  3. import (
  4. "os"
  5. "path/filepath"
  6. "strconv"
  7. "testing"
  8. "github.com/docker/docker/errdefs"
  9. "github.com/docker/docker/pkg/idtools"
  10. "github.com/docker/docker/quota"
  11. "gotest.tools/v3/assert"
  12. is "gotest.tools/v3/assert/cmp"
  13. )
  14. const (
  15. quotaSize = 1024 * 1024
  16. quotaSizeLiteral = "1M"
  17. )
  18. func TestQuota(t *testing.T) {
  19. if msg, ok := quota.CanTestQuota(); !ok {
  20. t.Skip(msg)
  21. }
  22. // get sparse xfs test image
  23. imageFileName, err := quota.PrepareQuotaTestImage(t)
  24. if err != nil {
  25. t.Fatal(err)
  26. }
  27. defer os.Remove(imageFileName)
  28. t.Run("testVolWithQuota", quota.WrapMountTest(imageFileName, true, testVolWithQuota))
  29. t.Run("testVolQuotaUnsupported", quota.WrapMountTest(imageFileName, false, testVolQuotaUnsupported))
  30. }
  31. func testVolWithQuota(t *testing.T, mountPoint, backingFsDev, testDir string) {
  32. r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
  33. if err != nil {
  34. t.Fatal(err)
  35. }
  36. assert.Assert(t, r.quotaCtl != nil)
  37. vol, err := r.Create("testing", map[string]string{"size": quotaSizeLiteral})
  38. if err != nil {
  39. t.Fatal(err)
  40. }
  41. dir, err := vol.Mount("1234")
  42. if err != nil {
  43. t.Fatal(err)
  44. }
  45. defer func() {
  46. if err := vol.Unmount("1234"); err != nil {
  47. t.Fatal(err)
  48. }
  49. }()
  50. testfile := filepath.Join(dir, "testfile")
  51. // test writing file smaller than quota
  52. assert.NilError(t, os.WriteFile(testfile, make([]byte, quotaSize/2), 0o644))
  53. assert.NilError(t, os.Remove(testfile))
  54. // test writing fiel larger than quota
  55. err = os.WriteFile(testfile, make([]byte, quotaSize+1), 0o644)
  56. assert.ErrorContains(t, err, "")
  57. if _, err := os.Stat(testfile); err == nil {
  58. assert.NilError(t, os.Remove(testfile))
  59. }
  60. }
  61. func testVolQuotaUnsupported(t *testing.T, mountPoint, backingFsDev, testDir string) {
  62. r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
  63. if err != nil {
  64. t.Fatal(err)
  65. }
  66. assert.Assert(t, is.Nil(r.quotaCtl))
  67. _, err = r.Create("testing", map[string]string{"size": quotaSizeLiteral})
  68. assert.ErrorContains(t, err, "no quota support")
  69. vol, err := r.Create("testing", nil)
  70. if err != nil {
  71. t.Fatal(err)
  72. }
  73. // this could happen if someone moves volumes from storage with
  74. // quota support to some place without
  75. lv, ok := vol.(*localVolume)
  76. assert.Assert(t, ok)
  77. lv.opts = &optsConfig{
  78. Quota: quota.Quota{Size: quotaSize},
  79. }
  80. _, err = vol.Mount("1234")
  81. assert.ErrorContains(t, err, "no quota support")
  82. }
  83. func TestVolCreateValidation(t *testing.T) {
  84. r, err := New(t.TempDir(), idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
  85. if err != nil {
  86. t.Fatal(err)
  87. }
  88. mandatoryOpts = map[string][]string{
  89. "device": {"type"},
  90. "type": {"device"},
  91. "o": {"device", "type"},
  92. }
  93. tests := []struct {
  94. doc string
  95. name string
  96. opts map[string]string
  97. expectedErr string
  98. }{
  99. {
  100. doc: "invalid: name too short",
  101. name: "a",
  102. opts: map[string]string{
  103. "type": "foo",
  104. "device": "foo",
  105. },
  106. expectedErr: `volume name is too short, names should be at least two alphanumeric characters`,
  107. },
  108. {
  109. doc: "invalid: name invalid characters",
  110. name: "hello world",
  111. opts: map[string]string{
  112. "type": "foo",
  113. "device": "foo",
  114. },
  115. expectedErr: `"hello world" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path`,
  116. },
  117. {
  118. doc: "invalid: unknown option",
  119. opts: map[string]string{"hello": "world"},
  120. expectedErr: `invalid option: "hello"`,
  121. },
  122. {
  123. doc: "invalid: invalid size",
  124. opts: map[string]string{"size": "hello"},
  125. expectedErr: `invalid size: 'hello'`,
  126. },
  127. {
  128. doc: "invalid: size, but no quotactl",
  129. opts: map[string]string{"size": "1234"},
  130. expectedErr: `quota size requested but no quota support`,
  131. },
  132. {
  133. doc: "invalid: device without type",
  134. opts: map[string]string{
  135. "device": "foo",
  136. },
  137. expectedErr: `missing required option: "type"`,
  138. },
  139. {
  140. doc: "invalid: type without device",
  141. opts: map[string]string{
  142. "type": "foo",
  143. },
  144. expectedErr: `missing required option: "device"`,
  145. },
  146. {
  147. doc: "invalid: o without device",
  148. opts: map[string]string{
  149. "o": "foo",
  150. "type": "foo",
  151. },
  152. expectedErr: `missing required option: "device"`,
  153. },
  154. {
  155. doc: "invalid: o without type",
  156. opts: map[string]string{
  157. "o": "foo",
  158. "device": "foo",
  159. },
  160. expectedErr: `missing required option: "type"`,
  161. },
  162. {
  163. doc: "valid: short name, no options",
  164. name: "ab",
  165. },
  166. {
  167. doc: "valid: device and type",
  168. opts: map[string]string{
  169. "type": "foo",
  170. "device": "foo",
  171. },
  172. },
  173. {
  174. doc: "valid: device, type, and o",
  175. opts: map[string]string{
  176. "type": "foo",
  177. "device": "foo",
  178. "o": "foo",
  179. },
  180. },
  181. }
  182. for i, tc := range tests {
  183. tc := tc
  184. t.Run(tc.doc, func(t *testing.T) {
  185. if tc.name == "" {
  186. tc.name = "vol-" + strconv.Itoa(i)
  187. }
  188. v, err := r.Create(tc.name, tc.opts)
  189. if v != nil {
  190. defer assert.Check(t, r.Remove(v))
  191. }
  192. if tc.expectedErr == "" {
  193. assert.NilError(t, err)
  194. } else {
  195. assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err)
  196. assert.ErrorContains(t, err, tc.expectedErr)
  197. }
  198. })
  199. }
  200. }