[MM-44489] Cloud limits: enforcing files (#20703)

* Update last accessible file time

* Filter fileInfos

* Set inaccessible header

* Fix lint issue

* Fix lint issue

* Fix i18n

* add nil check

* Fix merge conflicts

* Add helper functions to clear out inaccessible files content

* Remove content for inaccessible files

* Fix typo

* wip

* Remove InaccessibleContent field, instead use Archived

* Add store tests

* Add tests

* Add separate funcs to ignore cloud limits

* Use separate query for MySql

* Use GetReplicaX

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
Vishal
2022-09-28 22:22:53 +05:30
коммит произвёл GitHub
родитель b9834a2fc2
Коммит f5f036d94b
27 изменённых файлов: 984 добавлений и 21 удалений

Просмотреть файл

@@ -3527,6 +3527,24 @@ func (s *OpenTracingLayerFileInfoStore) GetStorageUsage(allowFromCache bool, inc
return result, err
}
func (s *OpenTracingLayerFileInfoStore) GetUptoNSizeFileTime(n int64) (int64, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "FileInfoStore.GetUptoNSizeFileTime")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.FileInfoStore.GetUptoNSizeFileTime(n)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerFileInfoStore) GetWithOptions(page int, perPage int, opt *model.GetFileInfosOptions) ([]*model.FileInfo, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "FileInfoStore.GetWithOptions")

Просмотреть файл

@@ -3957,6 +3957,27 @@ func (s *RetryLayerFileInfoStore) GetStorageUsage(allowFromCache bool, includeDe
}
func (s *RetryLayerFileInfoStore) GetUptoNSizeFileTime(n int64) (int64, error) {
tries := 0
for {
result, err := s.FileInfoStore.GetUptoNSizeFileTime(n)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerFileInfoStore) GetWithOptions(page int, perPage int, opt *model.GetFileInfosOptions) ([]*model.FileInfo, error) {
tries := 0

Просмотреть файл

@@ -753,3 +753,49 @@ func (fs SqlFileInfoStore) GetStorageUsage(allowFromCache, includeDeleted bool)
}
return size, nil
}
// GetUptoNSizeFileTime returns the CreateAt time of the last accessible file with a running-total size upto n bytes.
func (fs *SqlFileInfoStore) GetUptoNSizeFileTime(n int64) (int64, error) {
if n <= 0 {
return 0, errors.New("n can't be less than 1")
}
var sizeSubQuery sq.SelectBuilder
// Separate query for MySql, as current min-version 5.x doesn't support window-functions
if fs.DriverName() == model.DatabaseDriverMysql {
sizeSubQuery = sq.
Select("(@runningSum := @runningSum + fi.Size) RunningTotal", "fi.CreateAt").
From("FileInfo fi").
Join("(SELECT @runningSum := 0) as tmp").
Where(sq.Eq{"fi.DeleteAt": 0}).
OrderBy("fi.CreateAt DESC, fi.Id")
} else {
sizeSubQuery = sq.
Select("SUM(fi.Size) OVER(ORDER BY CreateAt DESC, fi.Id) RunningTotal", "fi.CreateAt").
From("FileInfo fi").
Where(sq.Eq{"fi.DeleteAt": 0})
}
builder := fs.getQueryBuilder().
Select("fi2.CreateAt").
FromSelect(sizeSubQuery, "fi2").
Where(sq.LtOrEq{"fi2.RunningTotal": n}).
OrderBy("fi2.CreateAt").
Limit(1)
query, queryArgs, err := builder.ToSql()
if err != nil {
return 0, errors.Wrap(err, "GetUptoNSizeFileTime_tosql")
}
var createAt int64
if err := fs.GetReplicaX().Get(&createAt, query, queryArgs...); err != nil {
if err == sql.ErrNoRows {
return 0, store.NewErrNotFound("File", "none")
}
return 0, errors.Wrapf(err, "failed to get the File for size upto=%d", n)
}
return createAt, nil
}

Просмотреть файл

@@ -698,6 +698,8 @@ type FileInfoStore interface {
GetFilesBatchForIndexing(startTime int64, startFileID string, limit int) ([]*model.FileForIndexing, error)
ClearCaches()
GetStorageUsage(allowFromCache, includeDeleted bool) (int64, error)
// GetUptoNSizeFileTime returns the CreateAt time of the last accessible file with a running-total size upto n bytes.
GetUptoNSizeFileTime(n int64) (int64, error)
}
type UploadSessionStore interface {

Просмотреть файл

@@ -11,6 +11,7 @@ import (
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/store"
"github.com/mattermost/mattermost-server/v6/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -30,6 +31,7 @@ func TestFileInfoStore(t *testing.T, ss store.Store) {
t.Run("GetFilesBatchForIndexing", func(t *testing.T) { testFileInfoStoreGetFilesBatchForIndexing(t, ss) })
t.Run("CountAll", func(t *testing.T) { testFileInfoStoreCountAll(t, ss) })
t.Run("GetStorageUsage", func(t *testing.T) { testFileInfoGetStorageUsage(t, ss) })
t.Run("GetUptoNSizeFileTime", func(t *testing.T) { testGetUptoNSizeFileTime(t, ss) })
}
func testFileInfoSaveGet(t *testing.T, ss store.Store) {
@@ -772,3 +774,71 @@ func testFileInfoGetStorageUsage(t *testing.T, ss store.Store) {
require.NoError(t, err)
require.Equal(t, int64(30), usage)
}
func testGetUptoNSizeFileTime(t *testing.T, ss store.Store) {
_, err := ss.FileInfo().GetUptoNSizeFileTime(0)
assert.Error(t, err)
_, err = ss.FileInfo().GetUptoNSizeFileTime(-1)
assert.Error(t, err)
_, err = ss.FileInfo().PermanentDeleteBatch(model.GetMillis(), 100000)
require.NoError(t, err)
diff := int64(10000)
now := utils.MillisFromTime(time.Now()) + diff
f1, err := ss.FileInfo().Save(&model.FileInfo{
PostId: model.NewId(),
CreatorId: model.NewId(),
Size: 10,
Path: "file1.txt",
CreateAt: now,
})
require.NoError(t, err)
now = now + diff
f2, err := ss.FileInfo().Save(&model.FileInfo{
PostId: model.NewId(),
CreatorId: model.NewId(),
Size: 10,
Path: "file2.txt",
CreateAt: now,
})
require.NoError(t, err)
now = now + diff
f3, err := ss.FileInfo().Save(&model.FileInfo{
PostId: model.NewId(),
CreatorId: model.NewId(),
Size: 10,
Path: "file3.txt",
CreateAt: now,
})
require.NoError(t, err)
now = now + diff
_, err = ss.FileInfo().Save(&model.FileInfo{
PostId: model.NewId(),
CreatorId: model.NewId(),
Size: 10,
Path: "file4.txt",
CreateAt: now,
})
require.NoError(t, err)
createAt, err := ss.FileInfo().GetUptoNSizeFileTime(20)
require.NoError(t, err)
assert.Equal(t, f3.CreateAt, createAt)
_, err = ss.FileInfo().GetUptoNSizeFileTime(5)
assert.Error(t, err)
assert.IsType(t, &store.ErrNotFound{}, err)
createAt, err = ss.FileInfo().GetUptoNSizeFileTime(1000)
require.NoError(t, err)
assert.Equal(t, f1.CreateAt, createAt)
_, err = ss.FileInfo().DeleteForPost(f3.PostId)
require.NoError(t, err)
createAt, err = ss.FileInfo().GetUptoNSizeFileTime(20)
require.NoError(t, err)
assert.Equal(t, f2.CreateAt, createAt)
}

Просмотреть файл

@@ -257,6 +257,27 @@ func (_m *FileInfoStore) GetStorageUsage(allowFromCache bool, includeDeleted boo
return r0, r1
}
// GetUptoNSizeFileTime provides a mock function with given fields: n
func (_m *FileInfoStore) GetUptoNSizeFileTime(n int64) (int64, error) {
ret := _m.Called(n)
var r0 int64
if rf, ok := ret.Get(0).(func(int64) int64); ok {
r0 = rf(n)
} else {
r0 = ret.Get(0).(int64)
}
var r1 error
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(n)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetWithOptions provides a mock function with given fields: page, perPage, opt
func (_m *FileInfoStore) GetWithOptions(page int, perPage int, opt *model.GetFileInfosOptions) ([]*model.FileInfo, error) {
ret := _m.Called(page, perPage, opt)

Просмотреть файл

@@ -3222,6 +3222,22 @@ func (s *TimerLayerFileInfoStore) GetStorageUsage(allowFromCache bool, includeDe
return result, err
}
func (s *TimerLayerFileInfoStore) GetUptoNSizeFileTime(n int64) (int64, error) {
start := time.Now()
result, err := s.FileInfoStore.GetUptoNSizeFileTime(n)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("FileInfoStore.GetUptoNSizeFileTime", success, elapsed)
}
return result, err
}
func (s *TimerLayerFileInfoStore) GetWithOptions(page int, perPage int, opt *model.GetFileInfosOptions) ([]*model.FileInfo, error) {
start := time.Now()