github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/volume/local/local_linux_test.go (about)

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