123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- //go:build linux
- package local // import "github.com/docker/docker/volume/local"
- import (
- "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",
- },
- },
- }
- 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)
- }
- })
- }
- }
|