github.com/moby/docker@v26.1.3+incompatible/volume/local/local_linux_test.go (about) 1 //go:build linux 2 3 package local // import "github.com/docker/docker/volume/local" 4 5 import ( 6 "net" 7 "os" 8 "path/filepath" 9 "strconv" 10 "testing" 11 12 "github.com/docker/docker/errdefs" 13 "github.com/docker/docker/pkg/idtools" 14 "github.com/docker/docker/quota" 15 "gotest.tools/v3/assert" 16 is "gotest.tools/v3/assert/cmp" 17 ) 18 19 const ( 20 quotaSize = 1024 * 1024 21 quotaSizeLiteral = "1M" 22 ) 23 24 func TestQuota(t *testing.T) { 25 if msg, ok := quota.CanTestQuota(); !ok { 26 t.Skip(msg) 27 } 28 29 // get sparse xfs test image 30 imageFileName, err := quota.PrepareQuotaTestImage(t) 31 if err != nil { 32 t.Fatal(err) 33 } 34 defer os.Remove(imageFileName) 35 36 t.Run("testVolWithQuota", quota.WrapMountTest(imageFileName, true, testVolWithQuota)) 37 t.Run("testVolQuotaUnsupported", quota.WrapMountTest(imageFileName, false, testVolQuotaUnsupported)) 38 } 39 40 func testVolWithQuota(t *testing.T, mountPoint, backingFsDev, testDir string) { 41 r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) 42 if err != nil { 43 t.Fatal(err) 44 } 45 assert.Assert(t, r.quotaCtl != nil) 46 47 vol, err := r.Create("testing", map[string]string{"size": quotaSizeLiteral}) 48 if err != nil { 49 t.Fatal(err) 50 } 51 52 dir, err := vol.Mount("1234") 53 if err != nil { 54 t.Fatal(err) 55 } 56 defer func() { 57 if err := vol.Unmount("1234"); err != nil { 58 t.Fatal(err) 59 } 60 }() 61 62 testfile := filepath.Join(dir, "testfile") 63 64 // test writing file smaller than quota 65 assert.NilError(t, os.WriteFile(testfile, make([]byte, quotaSize/2), 0o644)) 66 assert.NilError(t, os.Remove(testfile)) 67 68 // test writing fiel larger than quota 69 err = os.WriteFile(testfile, make([]byte, quotaSize+1), 0o644) 70 assert.ErrorContains(t, err, "") 71 if _, err := os.Stat(testfile); err == nil { 72 assert.NilError(t, os.Remove(testfile)) 73 } 74 } 75 76 func testVolQuotaUnsupported(t *testing.T, mountPoint, backingFsDev, testDir string) { 77 r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) 78 if err != nil { 79 t.Fatal(err) 80 } 81 assert.Assert(t, is.Nil(r.quotaCtl)) 82 83 _, err = r.Create("testing", map[string]string{"size": quotaSizeLiteral}) 84 assert.ErrorContains(t, err, "no quota support") 85 86 vol, err := r.Create("testing", nil) 87 if err != nil { 88 t.Fatal(err) 89 } 90 91 // this could happen if someone moves volumes from storage with 92 // quota support to some place without 93 lv, ok := vol.(*localVolume) 94 assert.Assert(t, ok) 95 lv.opts = &optsConfig{ 96 Quota: quota.Quota{Size: quotaSize}, 97 } 98 99 _, err = vol.Mount("1234") 100 assert.ErrorContains(t, err, "no quota support") 101 } 102 103 func TestVolCreateValidation(t *testing.T) { 104 r, err := New(t.TempDir(), idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()}) 105 if err != nil { 106 t.Fatal(err) 107 } 108 109 mandatoryOpts = map[string][]string{ 110 "device": {"type"}, 111 "type": {"device"}, 112 "o": {"device", "type"}, 113 } 114 115 tests := []struct { 116 doc string 117 name string 118 opts map[string]string 119 expectedErr string 120 }{ 121 { 122 doc: "invalid: name too short", 123 name: "a", 124 opts: map[string]string{ 125 "type": "foo", 126 "device": "foo", 127 }, 128 expectedErr: `volume name is too short, names should be at least two alphanumeric characters`, 129 }, 130 { 131 doc: "invalid: name invalid characters", 132 name: "hello world", 133 opts: map[string]string{ 134 "type": "foo", 135 "device": "foo", 136 }, 137 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`, 138 }, 139 { 140 doc: "invalid: unknown option", 141 opts: map[string]string{"hello": "world"}, 142 expectedErr: `invalid option: "hello"`, 143 }, 144 { 145 doc: "invalid: invalid size", 146 opts: map[string]string{"size": "hello"}, 147 expectedErr: `invalid size: 'hello'`, 148 }, 149 { 150 doc: "invalid: size, but no quotactl", 151 opts: map[string]string{"size": "1234"}, 152 expectedErr: `quota size requested but no quota support`, 153 }, 154 { 155 doc: "invalid: device without type", 156 opts: map[string]string{ 157 "device": "foo", 158 }, 159 expectedErr: `missing required option: "type"`, 160 }, 161 { 162 doc: "invalid: type without device", 163 opts: map[string]string{ 164 "type": "foo", 165 }, 166 expectedErr: `missing required option: "device"`, 167 }, 168 { 169 doc: "invalid: o without device", 170 opts: map[string]string{ 171 "o": "foo", 172 "type": "foo", 173 }, 174 expectedErr: `missing required option: "device"`, 175 }, 176 { 177 doc: "invalid: o without type", 178 opts: map[string]string{ 179 "o": "foo", 180 "device": "foo", 181 }, 182 expectedErr: `missing required option: "type"`, 183 }, 184 { 185 doc: "valid: short name, no options", 186 name: "ab", 187 }, 188 { 189 doc: "valid: device and type", 190 opts: map[string]string{ 191 "type": "foo", 192 "device": "foo", 193 }, 194 }, 195 { 196 doc: "valid: device, type, and o", 197 opts: map[string]string{ 198 "type": "foo", 199 "device": "foo", 200 "o": "foo", 201 }, 202 }, 203 { 204 doc: "cifs", 205 opts: map[string]string{ 206 "type": "cifs", 207 "device": "//some.example.com/thepath", 208 "o": "foo", 209 }, 210 }, 211 { 212 doc: "cifs with port in url", 213 opts: map[string]string{ 214 "type": "cifs", 215 "device": "//some.example.com:2345/thepath", 216 "o": "foo", 217 }, 218 expectedErr: "port not allowed in CIFS device URL, include 'port' in 'o='", 219 }, 220 { 221 doc: "cifs with bad url", 222 opts: map[string]string{ 223 "type": "cifs", 224 "device": ":::", 225 "o": "foo", 226 }, 227 expectedErr: `error parsing mount device url: parse ":::": missing protocol scheme`, 228 }, 229 } 230 231 for i, tc := range tests { 232 tc := tc 233 t.Run(tc.doc, func(t *testing.T) { 234 if tc.name == "" { 235 tc.name = "vol-" + strconv.Itoa(i) 236 } 237 v, err := r.Create(tc.name, tc.opts) 238 if v != nil { 239 defer assert.Check(t, r.Remove(v)) 240 } 241 if tc.expectedErr == "" { 242 assert.NilError(t, err) 243 } else { 244 assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err) 245 assert.ErrorContains(t, err, tc.expectedErr) 246 } 247 }) 248 } 249 } 250 251 func TestVolMountOpts(t *testing.T) { 252 tests := []struct { 253 name string 254 opts optsConfig 255 expectedErr string 256 expectedDevice, expectedOpts string 257 }{ 258 { 259 name: "cifs url with space", 260 opts: optsConfig{ 261 MountType: "cifs", 262 MountDevice: "//1.2.3.4/Program Files", 263 }, 264 expectedDevice: "//1.2.3.4/Program Files", 265 expectedOpts: "", 266 }, 267 { 268 name: "cifs resolve addr", 269 opts: optsConfig{ 270 MountType: "cifs", 271 MountDevice: "//example.com/Program Files", 272 MountOpts: "addr=example.com", 273 }, 274 expectedDevice: "//example.com/Program Files", 275 expectedOpts: "addr=1.2.3.4", 276 }, 277 { 278 name: "cifs resolve device", 279 opts: optsConfig{ 280 MountType: "cifs", 281 MountDevice: "//example.com/Program Files", 282 }, 283 expectedDevice: "//1.2.3.4/Program Files", 284 }, 285 { 286 name: "nfs dont resolve device", 287 opts: optsConfig{ 288 MountType: "nfs", 289 MountDevice: "//example.com/Program Files", 290 }, 291 expectedDevice: "//example.com/Program Files", 292 }, 293 { 294 name: "nfs resolve addr", 295 opts: optsConfig{ 296 MountType: "nfs", 297 MountDevice: "//example.com/Program Files", 298 MountOpts: "addr=example.com", 299 }, 300 expectedDevice: "//example.com/Program Files", 301 expectedOpts: "addr=1.2.3.4", 302 }, 303 } 304 305 ip1234 := net.ParseIP("1.2.3.4") 306 resolveIP := func(network, addr string) (*net.IPAddr, error) { 307 switch addr { 308 case "example.com": 309 return &net.IPAddr{IP: ip1234}, nil 310 } 311 312 return nil, &net.DNSError{Err: "no such host", Name: addr, IsNotFound: true} 313 } 314 315 for _, tc := range tests { 316 tc := tc 317 t.Run(tc.name, func(t *testing.T) { 318 dev, opts, err := getMountOptions(&tc.opts, resolveIP) 319 320 if tc.expectedErr != "" { 321 assert.Check(t, is.ErrorContains(err, tc.expectedErr)) 322 } else { 323 assert.Check(t, err) 324 } 325 326 assert.Check(t, is.Equal(dev, tc.expectedDevice)) 327 assert.Check(t, is.Equal(opts, tc.expectedOpts)) 328 }) 329 } 330 }