github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/volume/local/local_linux_test.go (about)

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