Skip to content

Commit ab42e02

Browse files
committed
Make QuotaEnforcingFilePool.NewFile() work correctly with non-zero size
We forgot to check the provided size and set it in the file structure. This would cause subsequent writes and truncation calls to get the size wrong.
1 parent 3161b6c commit ab42e02

File tree

2 files changed

+51
-24
lines changed

2 files changed

+51
-24
lines changed

pkg/filesystem/pool/quota_enforcing_file_pool.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ func (fp *quotaEnforcingFilePool) NewFile(holeSource HoleSource, size uint64) (f
5757
if !fp.filesRemaining.allocate(1) {
5858
return nil, status.Error(codes.InvalidArgument, "File count quota reached")
5959
}
60+
if size > 0 && !fp.bytesRemaining.allocate(size) {
61+
fp.filesRemaining.release(1)
62+
return nil, status.Error(codes.InvalidArgument, "File size quota reached")
63+
}
6064
f, err := fp.base.NewFile(holeSource, size)
6165
if err != nil {
6266
fp.filesRemaining.release(1)
@@ -65,6 +69,7 @@ func (fp *quotaEnforcingFilePool) NewFile(holeSource HoleSource, size uint64) (f
6569
return &quotaEnforcingFile{
6670
FileReadWriter: f,
6771
pool: fp,
72+
size: size,
6873
}, nil
6974
}
7075

pkg/filesystem/pool/quota_enforcing_file_pool_test.go

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func testRemainingQuota(t *testing.T, ctrl *gomock.Controller, underlyingPool *m
3333
_, err := filePool.NewFile(pool.ZeroHoleSource, 0)
3434
require.Equal(t, err, status.Error(codes.InvalidArgument, "File count quota reached"))
3535
for i := 0; i < filesRemaining; i++ {
36-
underlyingFiles[i].EXPECT().Close().Return(nil)
36+
underlyingFiles[i].EXPECT().Close()
3737
require.NoError(t, files[i].Close())
3838
}
3939

@@ -44,11 +44,11 @@ func testRemainingQuota(t *testing.T, ctrl *gomock.Controller, underlyingPool *m
4444
f, err := filePool.NewFile(pool.ZeroHoleSource, 0)
4545
require.NoError(t, err)
4646
if bytesRemaining != 0 {
47-
underlyingFile.EXPECT().Truncate(bytesRemaining).Return(nil)
47+
underlyingFile.EXPECT().Truncate(bytesRemaining)
4848
}
4949
require.NoError(t, f.Truncate(bytesRemaining))
5050
require.Equal(t, f.Truncate(bytesRemaining+1), status.Error(codes.InvalidArgument, "File size quota reached"))
51-
underlyingFile.EXPECT().Close().Return(nil)
51+
underlyingFile.EXPECT().Close()
5252
require.NoError(t, f.Close())
5353
}
5454

@@ -68,73 +68,95 @@ func TestQuotaEnforcingFilePoolExample(t *testing.T) {
6868
testRemainingQuota(t, ctrl, underlyingPool, filePool, 10, 1000)
6969

7070
// Successfully allocate a file.
71-
underlyingFile := mock.NewMockFileReadWriter(ctrl)
72-
underlyingPool.EXPECT().NewFile(pool.ZeroHoleSource, uint64(0)).Return(underlyingFile, nil)
73-
f, err := filePool.NewFile(pool.ZeroHoleSource, 0)
71+
underlyingFile1 := mock.NewMockFileReadWriter(ctrl)
72+
underlyingPool.EXPECT().NewFile(pool.ZeroHoleSource, uint64(0)).Return(underlyingFile1, nil)
73+
f1, err := filePool.NewFile(pool.ZeroHoleSource, 0)
7474
require.NoError(t, err)
7575
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 1000)
7676

7777
// Read calls should be forwarded properly.
7878
var p [10]byte
79-
underlyingFile.EXPECT().ReadAt(p[:], int64(123)).Return(0, io.EOF)
80-
n, err := f.ReadAt(p[:], 123)
79+
underlyingFile1.EXPECT().ReadAt(p[:], int64(123)).Return(0, io.EOF)
80+
n, err := f1.ReadAt(p[:], 123)
8181
require.Equal(t, 0, n)
8282
require.Equal(t, io.EOF, err)
8383
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 1000)
8484

8585
// Writes that would cause the file to grow beyond the maximum
8686
// size should be disallowed.
87-
n, err = f.WriteAt(p[:], 991)
87+
n, err = f1.WriteAt(p[:], 991)
8888
require.Equal(t, 0, n)
8989
require.Equal(t, err, status.Error(codes.InvalidArgument, "File size quota reached"))
9090
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 1000)
9191

9292
// A failed write should initially allocate all of the required
9393
// space, but release the full amount once more.
94-
underlyingFile.EXPECT().WriteAt(p[:], int64(990)).Return(0, status.Error(codes.Internal, "Cannot write data at all"))
95-
n, err = f.WriteAt(p[:], 990)
94+
underlyingFile1.EXPECT().WriteAt(p[:], int64(990)).Return(0, status.Error(codes.Internal, "Cannot write data at all"))
95+
n, err = f1.WriteAt(p[:], 990)
9696
require.Equal(t, 0, n)
9797
require.Equal(t, err, status.Error(codes.Internal, "Cannot write data at all"))
9898
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 1000)
9999

100100
// A short write should initially allocate all of the required
101101
// space, but release the amount of data that was not written.
102-
underlyingFile.EXPECT().WriteAt(p[:], int64(990)).Return(7, status.Error(codes.Internal, "Disk died in the middle of the write"))
103-
n, err = f.WriteAt(p[:], 990)
102+
underlyingFile1.EXPECT().WriteAt(p[:], int64(990)).Return(7, status.Error(codes.Internal, "Disk died in the middle of the write"))
103+
n, err = f1.WriteAt(p[:], 990)
104104
require.Equal(t, 7, n)
105105
require.Equal(t, err, status.Error(codes.Internal, "Disk died in the middle of the write"))
106106
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 3)
107107

108108
// I/O error while shrinking file should not cause the quotas to
109109
// be affected.
110-
underlyingFile.EXPECT().Truncate(int64(123)).Return(status.Error(codes.Internal, "Failed to adjust inode"))
111-
require.Equal(t, f.Truncate(123), status.Error(codes.Internal, "Failed to adjust inode"))
110+
underlyingFile1.EXPECT().Truncate(int64(123)).Return(status.Error(codes.Internal, "Failed to adjust inode"))
111+
require.Equal(t, f1.Truncate(123), status.Error(codes.Internal, "Failed to adjust inode"))
112112
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 3)
113113

114114
// Successfully shrinking the file.
115-
underlyingFile.EXPECT().Truncate(int64(123)).Return(nil)
116-
require.NoError(t, f.Truncate(123))
115+
underlyingFile1.EXPECT().Truncate(int64(123))
116+
require.NoError(t, f1.Truncate(123))
117117
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 877)
118118

119119
// Growing the file past the permitted size should not be
120120
// allowed.
121-
require.Equal(t, f.Truncate(1001), status.Error(codes.InvalidArgument, "File size quota reached"))
121+
require.Equal(t, f1.Truncate(1001), status.Error(codes.InvalidArgument, "File size quota reached"))
122122
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 877)
123123

124124
// I/O error while growing file should not cause the quotas to
125125
// be affected.
126-
underlyingFile.EXPECT().Truncate(int64(1000)).Return(status.Error(codes.Internal, "Failed to adjust inode"))
127-
require.Equal(t, f.Truncate(1000), status.Error(codes.Internal, "Failed to adjust inode"))
126+
underlyingFile1.EXPECT().Truncate(int64(1000)).Return(status.Error(codes.Internal, "Failed to adjust inode"))
127+
require.Equal(t, f1.Truncate(1000), status.Error(codes.Internal, "Failed to adjust inode"))
128128
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 877)
129129

130130
// Successfully growing the file.
131-
underlyingFile.EXPECT().Truncate(int64(1000)).Return(nil)
132-
require.NoError(t, f.Truncate(1000))
131+
underlyingFile1.EXPECT().Truncate(int64(1000))
132+
require.NoError(t, f1.Truncate(1000))
133133
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 0)
134134

135135
// Closing the file should bring the pool back in the initial
136136
// state.
137-
underlyingFile.EXPECT().Close().Return(nil)
138-
require.NoError(t, f.Close())
137+
underlyingFile1.EXPECT().Close()
138+
require.NoError(t, f1.Close())
139+
testRemainingQuota(t, ctrl, underlyingPool, filePool, 10, 1000)
140+
141+
// Allocating a file with an initially provided size should
142+
// cause the remaining size to be adjusted as well.
143+
underlyingFile2 := mock.NewMockFileReadWriter(ctrl)
144+
underlyingPool.EXPECT().NewFile(pool.ZeroHoleSource, uint64(100)).Return(underlyingFile2, nil)
145+
f2, err := filePool.NewFile(pool.ZeroHoleSource, 100)
146+
require.NoError(t, err)
147+
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 900)
148+
149+
underlyingFile2.EXPECT().Truncate(int64(0))
150+
require.NoError(t, f2.Truncate(0))
151+
testRemainingQuota(t, ctrl, underlyingPool, filePool, 9, 1000)
152+
153+
underlyingFile2.EXPECT().Close()
154+
require.NoError(t, f2.Close())
155+
testRemainingQuota(t, ctrl, underlyingPool, filePool, 10, 1000)
156+
157+
// It shouldn't be possible to create a file whose size exceeds
158+
// the remaining file size quota.
159+
_, err = filePool.NewFile(pool.ZeroHoleSource, 1001)
160+
require.Equal(t, err, status.Error(codes.InvalidArgument, "File size quota reached"))
139161
testRemainingQuota(t, ctrl, underlyingPool, filePool, 10, 1000)
140162
}

0 commit comments

Comments
 (0)