123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- //go:build linux
- package local // import "github.com/docker/docker/volume/local"
- import (
- "net"
- "os"
- "path/filepath"
- "strconv"
- "testing"
- "github.com/docker/docker/errdefs"
- "github.com/docker/docker/pkg/idtools"
- "github.com/docker/docker/quota"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- )
- const (
- quotaSize = 1024 * 1024
- quotaSizeLiteral = "1M"
- )
- func TestQuota(t *testing.T) {
- if msg, ok := quota.CanTestQuota(); !ok {
- t.Skip(msg)
- }
- // get sparse xfs test image
- imageFileName, err := quota.PrepareQuotaTestImage(t)
- if err != nil {
- t.Fatal(err)
- }
- defer os.Remove(imageFileName)
- t.Run("testVolWithQuota", quota.WrapMountTest(imageFileName, true, testVolWithQuota))
- t.Run("testVolQuotaUnsupported", quota.WrapMountTest(imageFileName, false, testVolQuotaUnsupported))
- }
- func testVolWithQuota(t *testing.T, mountPoint, backingFsDev, testDir string) {
- r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
- if err != nil {
- t.Fatal(err)
- }
- assert.Assert(t, r.quotaCtl != nil)
- vol, err := r.Create("testing", map[string]string{"size": quotaSizeLiteral})
- if err != nil {
- t.Fatal(err)
- }
- dir, err := vol.Mount("1234")
- if err != nil {
- t.Fatal(err)
- }
- defer func() {
- if err := vol.Unmount("1234"); err != nil {
- t.Fatal(err)
- }
- }()
- testfile := filepath.Join(dir, "testfile")
- // test writing file smaller than quota
- assert.NilError(t, os.WriteFile(testfile, make([]byte, quotaSize/2), 0o644))
- assert.NilError(t, os.Remove(testfile))
- // test writing fiel larger than quota
- err = os.WriteFile(testfile, make([]byte, quotaSize+1), 0o644)
- assert.ErrorContains(t, err, "")
- if _, err := os.Stat(testfile); err == nil {
- assert.NilError(t, os.Remove(testfile))
- }
- }
- func testVolQuotaUnsupported(t *testing.T, mountPoint, backingFsDev, testDir string) {
- r, err := New(testDir, idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
- if err != nil {
- t.Fatal(err)
- }
- assert.Assert(t, is.Nil(r.quotaCtl))
- _, err = r.Create("testing", map[string]string{"size": quotaSizeLiteral})
- assert.ErrorContains(t, err, "no quota support")
- vol, err := r.Create("testing", nil)
- if err != nil {
- t.Fatal(err)
- }
- // this could happen if someone moves volumes from storage with
- // quota support to some place without
- lv, ok := vol.(*localVolume)
- assert.Assert(t, ok)
- lv.opts = &optsConfig{
- Quota: quota.Quota{Size: quotaSize},
- }
- _, err = vol.Mount("1234")
- assert.ErrorContains(t, err, "no quota support")
- }
- func TestVolCreateValidation(t *testing.T) {
- r, err := New(t.TempDir(), idtools.Identity{UID: os.Geteuid(), GID: os.Getegid()})
- if err != nil {
- t.Fatal(err)
- }
- mandatoryOpts = map[string][]string{
- "device": {"type"},
- "type": {"device"},
- "o": {"device", "type"},
- }
- tests := []struct {
- doc string
- name string
- opts map[string]string
- expectedErr string
- }{
- {
- doc: "invalid: name too short",
- name: "a",
- opts: map[string]string{
- "type": "foo",
- "device": "foo",
- },
- expectedErr: `volume name is too short, names should be at least two alphanumeric characters`,
- },
- {
- doc: "invalid: name invalid characters",
- name: "hello world",
- opts: map[string]string{
- "type": "foo",
- "device": "foo",
- },
- 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`,
- },
- {
- doc: "invalid: unknown option",
- opts: map[string]string{"hello": "world"},
- expectedErr: `invalid option: "hello"`,
- },
- {
- doc: "invalid: invalid size",
- opts: map[string]string{"size": "hello"},
- expectedErr: `invalid size: 'hello'`,
- },
- {
- doc: "invalid: size, but no quotactl",
- opts: map[string]string{"size": "1234"},
- expectedErr: `quota size requested but no quota support`,
- },
- {
- doc: "invalid: device without type",
- opts: map[string]string{
- "device": "foo",
- },
- expectedErr: `missing required option: "type"`,
- },
- {
- doc: "invalid: type without device",
- opts: map[string]string{
- "type": "foo",
- },
- expectedErr: `missing required option: "device"`,
- },
- {
- doc: "invalid: o without device",
- opts: map[string]string{
- "o": "foo",
- "type": "foo",
- },
- expectedErr: `missing required option: "device"`,
- },
- {
- doc: "invalid: o without type",
- opts: map[string]string{
- "o": "foo",
- "device": "foo",
- },
- expectedErr: `missing required option: "type"`,
- },
- {
- doc: "valid: short name, no options",
- name: "ab",
- },
- {
- doc: "valid: device and type",
- opts: map[string]string{
- "type": "foo",
- "device": "foo",
- },
- },
- {
- doc: "valid: device, type, and o",
- opts: map[string]string{
- "type": "foo",
- "device": "foo",
- "o": "foo",
- },
- },
- {
- doc: "cifs",
- opts: map[string]string{
- "type": "cifs",
- "device": "//some.example.com/thepath",
- "o": "foo",
- },
- },
- {
- doc: "cifs with port in url",
- opts: map[string]string{
- "type": "cifs",
- "device": "//some.example.com:2345/thepath",
- "o": "foo",
- },
- expectedErr: "port not allowed in CIFS device URL, include 'port' in 'o='",
- },
- {
- doc: "cifs with bad url",
- opts: map[string]string{
- "type": "cifs",
- "device": ":::",
- "o": "foo",
- },
- expectedErr: `error parsing mount device url: parse ":::": missing protocol scheme`,
- },
- }
- for i, tc := range tests {
- tc := tc
- t.Run(tc.doc, func(t *testing.T) {
- if tc.name == "" {
- tc.name = "vol-" + strconv.Itoa(i)
- }
- v, err := r.Create(tc.name, tc.opts)
- if v != nil {
- defer assert.Check(t, r.Remove(v))
- }
- if tc.expectedErr == "" {
- assert.NilError(t, err)
- } else {
- assert.Check(t, errdefs.IsInvalidParameter(err), "got: %T", err)
- assert.ErrorContains(t, err, tc.expectedErr)
- }
- })
- }
- }
- func TestVolMountOpts(t *testing.T) {
- tests := []struct {
- name string
- opts optsConfig
- expectedErr string
- expectedDevice, expectedOpts string
- }{
- {
- name: "cifs url with space",
- opts: optsConfig{
- MountType: "cifs",
- MountDevice: "//1.2.3.4/Program Files",
- },
- expectedDevice: "//1.2.3.4/Program Files",
- expectedOpts: "",
- },
- {
- name: "cifs resolve addr",
- opts: optsConfig{
- MountType: "cifs",
- MountDevice: "//example.com/Program Files",
- MountOpts: "addr=example.com",
- },
- expectedDevice: "//example.com/Program Files",
- expectedOpts: "addr=1.2.3.4",
- },
- {
- name: "cifs resolve device",
- opts: optsConfig{
- MountType: "cifs",
- MountDevice: "//example.com/Program Files",
- },
- expectedDevice: "//1.2.3.4/Program Files",
- },
- {
- name: "nfs dont resolve device",
- opts: optsConfig{
- MountType: "nfs",
- MountDevice: "//example.com/Program Files",
- },
- expectedDevice: "//example.com/Program Files",
- },
- {
- name: "nfs resolve addr",
- opts: optsConfig{
- MountType: "nfs",
- MountDevice: "//example.com/Program Files",
- MountOpts: "addr=example.com",
- },
- expectedDevice: "//example.com/Program Files",
- expectedOpts: "addr=1.2.3.4",
- },
- }
- ip1234 := net.ParseIP("1.2.3.4")
- resolveIP := func(network, addr string) (*net.IPAddr, error) {
- switch addr {
- case "example.com":
- return &net.IPAddr{IP: ip1234}, nil
- }
- return nil, &net.DNSError{Err: "no such host", Name: addr, IsNotFound: true}
- }
- for _, tc := range tests {
- tc := tc
- t.Run(tc.name, func(t *testing.T) {
- dev, opts, err := getMountOptions(&tc.opts, resolveIP)
- if tc.expectedErr != "" {
- assert.Check(t, is.ErrorContains(err, tc.expectedErr))
- } else {
- assert.Check(t, err)
- }
- assert.Check(t, is.Equal(dev, tc.expectedDevice))
- assert.Check(t, is.Equal(opts, tc.expectedOpts))
- })
- }
- }
|