* Renamed user limit API to app limit API

* Added post warning limit

* Added tests

* Fixed types

* Renamed AppLimits to ServerLimits

* Fixed tests and review fixes

* Updated generated code

* Updated server i18n

* Fixed TestCreateUserOrGuest test

* Exclude deleted posts from post count for liims

* Reduced limits for ease of testing

* Restored original limts
Этот коммит содержится в:
Harshil Sharma
2024-04-18 11:50:30 +05:30
коммит произвёл GitHub
родитель 4571c6e3a3
Коммит b4a1b33d39
31 изменённых файлов: 448 добавлений и 153 удалений

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

@@ -13,22 +13,22 @@ import (
)
func (api *API) InitLimits() {
api.BaseRoutes.Limits.Handle("/users", api.APISessionRequired(getUserLimits)).Methods("GET")
api.BaseRoutes.Limits.Handle("/server", api.APISessionRequired(getServerLimits)).Methods("GET")
}
func getUserLimits(c *Context, w http.ResponseWriter, r *http.Request) {
func getServerLimits(c *Context, w http.ResponseWriter, r *http.Request) {
if !(c.IsSystemAdmin() && c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleReadUserManagementUsers)) {
c.SetPermissionError(model.PermissionSysconsoleReadUserManagementUsers)
return
}
userLimits, err := c.App.GetUserLimits()
serverLimits, err := c.App.GetServerLimits()
if err != nil {
c.Err = err
return
}
if err := json.NewEncoder(w).Encode(userLimits); err != nil {
c.Logger.Error("Error writing user limits response", mlog.Err(err))
if err := json.NewEncoder(w).Encode(serverLimits); err != nil {
c.Logger.Error("Error writing server limits response", mlog.Err(err))
}
}

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

@@ -806,6 +806,7 @@ type AppIface interface {
GetSchemeRolesForTeam(teamID string) (string, string, string, *model.AppError)
GetSchemes(scope string, offset int, limit int) ([]*model.Scheme, *model.AppError)
GetSchemesPage(scope string, page int, perPage int) ([]*model.Scheme, *model.AppError)
GetServerLimits() (*model.ServerLimits, *model.AppError)
GetSession(token string) (*model.Session, *model.AppError)
GetSessionById(c request.CTX, sessionID string) (*model.Session, *model.AppError)
GetSessions(c request.CTX, userID string) ([]*model.Session, *model.AppError)
@@ -864,7 +865,6 @@ type AppIface interface {
GetUserByUsername(username string) (*model.User, *model.AppError)
GetUserCountForReport(filter *model.UserReportOptions) (*int64, *model.AppError)
GetUserForLogin(c request.CTX, id, loginId string) (*model.User, *model.AppError)
GetUserLimits() (*model.UserLimits, *model.AppError)
GetUserTermsOfService(userID string) (*model.UserTermsOfService, *model.AppError)
GetUsers(userIDs []string) ([]*model.User, *model.AppError)
GetUsersByGroupChannelIds(c request.CTX, channelIDs []string, asAdmin bool) (map[string][]*model.User, *model.AppError)

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

@@ -12,26 +12,39 @@ import (
)
const (
maxUsersLimit = 10000
maxUsersHardLimit = 11000
maxUsersLimit = 10_000
maxUsersHardLimit = 11_000
maxPostLimit = 5_000_000
)
func (a *App) GetUserLimits() (*model.UserLimits, *model.AppError) {
if !a.shouldShowUserLimits() {
return &model.UserLimits{}, nil
func (a *App) GetServerLimits() (*model.ServerLimits, *model.AppError) {
var limits = &model.ServerLimits{}
if a.shouldShowUserLimits() {
activeUserCount, appErr := a.Srv().Store().User().Count(model.UserCountOptions{})
if appErr != nil {
mlog.Error("Failed to get active user count from database", mlog.String("error", appErr.Error()))
return nil, model.NewAppError("GetServerLimits", "app.limits.get_app_limits.user_count.store_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
}
limits.ActiveUserCount = activeUserCount
limits.MaxUsersLimit = maxUsersLimit
limits.MaxUsersHardLimit = maxUsersHardLimit
}
activeUserCount, appErr := a.Srv().Store().User().Count(model.UserCountOptions{})
if appErr != nil {
mlog.Error("Failed to get active user count from database", mlog.String("error", appErr.Error()))
return nil, model.NewAppError("GetUsersLimits", "app.limits.get_user_limits.user_count.store_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
if a.shouldShowPostLimits() {
postCount, appErr := a.Srv().Store().Post().AnalyticsPostCount(&model.PostCountOptions{ExcludeDeleted: true})
if appErr != nil {
mlog.Error("Failed to get post count from database", mlog.String("error", appErr.Error()))
return nil, model.NewAppError("GetServerLimits", "app.limits.get_server_limits.post_count.store_error", nil, "", http.StatusInternalServerError).Wrap(appErr)
}
limits.MaxPostLimit = maxPostLimit
limits.PostCount = postCount
}
return &model.UserLimits{
ActiveUserCount: activeUserCount,
MaxUsersLimit: maxUsersLimit,
MaxUsersHardLimit: maxUsersHardLimit,
}, nil
return limits, nil
}
func (a *App) shouldShowUserLimits() bool {
@@ -42,8 +55,16 @@ func (a *App) shouldShowUserLimits() bool {
return a.License() == nil
}
func (a *App) shouldShowPostLimits() bool {
if maxPostLimit == 0 {
return false
}
return a.License() == nil
}
func (a *App) isHardUserLimitExceeded() (bool, *model.AppError) {
userLimits, appErr := a.GetUserLimits()
userLimits, appErr := a.GetServerLimits()
if appErr != nil {
return false, appErr
}

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

@@ -6,127 +6,177 @@ package app
import (
"testing"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/public/model"
"github.com/stretchr/testify/require"
)
func TestGetUserLimits(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
func TestGetServerLimits(t *testing.T) {
t.Run("base case", func(t *testing.T) {
userLimits, appErr := th.App.GetUserLimits()
th := Setup(t).InitBasic()
defer th.TearDown()
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
// InitBasic creates 3 users by default
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(10000), userLimits.MaxUsersLimit)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
require.Equal(t, int64(10000), serverLimits.MaxUsersLimit)
// 5 posts are created by default
require.Equal(t, int64(5), serverLimits.PostCount)
require.Equal(t, int64(5_000_000), serverLimits.MaxPostLimit)
})
t.Run("user count should increase on creating new user and decrease on permanently deleting", func(t *testing.T) {
userLimits, appErr := th.App.GetUserLimits()
th := Setup(t).InitBasic()
defer th.TearDown()
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new user
newUser := th.CreateUser()
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(4), userLimits.ActiveUserCount)
require.Equal(t, int64(4), serverLimits.ActiveUserCount)
// now we'll delete the user
_ = th.App.PermanentDeleteUser(th.Context, newUser)
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("user count should increase on creating new guest user and decrease on permanently deleting", func(t *testing.T) {
userLimits, appErr := th.App.GetUserLimits()
th := Setup(t).InitBasic()
defer th.TearDown()
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new user
newGuestUser := th.CreateGuest()
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(4), userLimits.ActiveUserCount)
require.Equal(t, int64(4), serverLimits.ActiveUserCount)
// now we'll delete the user
_ = th.App.PermanentDeleteUser(th.Context, newGuestUser)
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("user count should increase on creating new user and decrease on soft deleting", func(t *testing.T) {
userLimits, appErr := th.App.GetUserLimits()
th := Setup(t).InitBasic()
defer th.TearDown()
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new user
newUser := th.CreateUser()
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(4), userLimits.ActiveUserCount)
require.Equal(t, int64(4), serverLimits.ActiveUserCount)
// now we'll delete the user
_, appErr = th.App.UpdateActive(th.Context, newUser, false)
require.Nil(t, appErr)
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("user count should increase on creating new guest user and decrease on soft deleting", func(t *testing.T) {
userLimits, appErr := th.App.GetUserLimits()
th := Setup(t).InitBasic()
defer th.TearDown()
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new user
newGuestUser := th.CreateGuest()
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(4), userLimits.ActiveUserCount)
require.Equal(t, int64(4), serverLimits.ActiveUserCount)
// now we'll delete the user
_, appErr = th.App.UpdateActive(th.Context, newGuestUser, false)
require.Nil(t, appErr)
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("user count should not change on creating or deleting bots", func(t *testing.T) {
userLimits, appErr := th.App.GetUserLimits()
th := Setup(t).InitBasic()
defer th.TearDown()
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we create a new bot
newBot := th.CreateBot()
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
// now we'll delete the bot
_ = th.App.PermanentDeleteBot(th.Context, newBot.UserId)
userLimits, appErr = th.App.GetUserLimits()
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(3), userLimits.ActiveUserCount)
require.Equal(t, int64(3), serverLimits.ActiveUserCount)
})
t.Run("limits should be empty when there is a license", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
th.App.Srv().SetLicense(model.NewTestLicense())
userLimits, appErr := th.App.GetUserLimits()
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(0), userLimits.ActiveUserCount)
require.Equal(t, int64(0), userLimits.MaxUsersLimit)
require.Equal(t, int64(0), serverLimits.ActiveUserCount)
require.Equal(t, int64(0), serverLimits.MaxUsersLimit)
})
t.Run("post count should increase on creating new post and should decrease on deleting post", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
serverLimits, appErr := th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(5), serverLimits.PostCount)
// now we create a new post
team := th.CreateTeam()
channel := th.CreateChannel(request.TestContext(t), team)
post := th.CreatePost(channel)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(6), serverLimits.PostCount)
// now we'll delete the post
_, appErr = th.App.DeletePost(request.TestContext(t), post.Id, "")
require.Nil(t, appErr)
serverLimits, appErr = th.App.GetServerLimits()
require.Nil(t, appErr)
require.Equal(t, int64(5), serverLimits.PostCount)
})
}

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

@@ -9463,6 +9463,28 @@ func (a *OpenTracingAppLayer) GetSchemesPage(scope string, page int, perPage int
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetServerLimits() (*model.ServerLimits, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetServerLimits")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetServerLimits()
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetSession(token string) (*model.Session, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetSession")
@@ -10851,28 +10873,6 @@ func (a *OpenTracingAppLayer) GetUserForLogin(c request.CTX, id string, loginId
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetUserLimits() (*model.UserLimits, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetUserLimits")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetUserLimits()
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetUserStatusesByIds(userIDs []string) ([]*model.Status, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetUserStatusesByIds")

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

@@ -333,7 +333,7 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m
}(ruser.Id)
}
userLimits, limitErr := a.GetUserLimits()
userLimits, limitErr := a.GetServerLimits()
if limitErr != nil {
// we don't want to break the create user flow just because of this.
// So, we log the error, not return
@@ -1070,7 +1070,7 @@ func (a *App) UpdateActive(c request.CTX, user *model.User, active bool) (*model
}
if active {
userLimits, appErr := a.GetUserLimits()
userLimits, appErr := a.GetServerLimits()
if appErr != nil {
mlog.Error("Error fetching user limits in UpdateActive", mlog.Err(appErr))
} else {

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

@@ -2033,8 +2033,12 @@ func TestCreateUserOrGuest(t *testing.T) {
mockUserStore := storemocks.UserStore{}
mockUserStore.On("Count", mock.Anything).Return(int64(12000), nil)
mockPostStore := storemocks.PostStore{}
mockPostStore.On("AnalyticsPostCount", mock.Anything).Return(int64(1000), nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
user := &model.User{
Email: "TestCreateUserOrGuest@example.com",
@@ -2147,8 +2151,12 @@ func userCreationMocks(t *testing.T, th *TestHelper, userID string, activeUserCo
mockProductNoticeStore := storemocks.ProductNoticesStore{}
mockProductNoticeStore.On("View", userID, mock.Anything).Return(nil)
mockPostStore := storemocks.PostStore{}
mockPostStore.On("AnalyticsPostCount", mock.Anything).Return(int64(1000), nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("Group").Return(&mockGroupStore)
mockStore.On("Channel").Return(&mockChannelStore)
mockStore.On("Preference").Return(&mockPreferencesStore)

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

@@ -6039,9 +6039,13 @@
"translation": "No license present"
},
{
"id": "app.limits.get_user_limits.user_count.store_error",
"id": "app.limits.get_app_limits.user_count.store_error",
"translation": "Failed to get user count"
},
{
"id": "app.limits.get_server_limits.post_count.store_error",
"translation": "Failed to get post count"
},
{
"id": "app.login.doLogin.updateLastLogin.error",
"translation": "Could not update last login timestamp"

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

@@ -8953,20 +8953,20 @@ func (c *Client4) SubmitTrueUpReview(ctx context.Context, req map[string]any) (*
return BuildResponse(r), nil
}
func (c *Client4) GetUserLimits(ctx context.Context) (*UserLimits, *Response, error) {
func (c *Client4) GetServerLimits(ctx context.Context) (*ServerLimits, *Response, error) {
r, err := c.DoAPIGet(ctx, c.limitsRoute()+"/users", "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var userLimits UserLimits
var serverLimits ServerLimits
if r.StatusCode == http.StatusNotModified {
return &userLimits, BuildResponse(r), nil
return &serverLimits, BuildResponse(r), nil
}
if err := json.NewDecoder(r.Body).Decode(&userLimits); err != nil {
return nil, nil, NewAppError("GetUserLimits", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
if err := json.NewDecoder(r.Body).Decode(&serverLimits); err != nil {
return nil, nil, NewAppError("GetServerLimits", "api.unmarshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return &userLimits, BuildResponse(r), nil
return &serverLimits, BuildResponse(r), nil
}
// CreateChannelBookmark creates a channel bookmark based on the provided struct.

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

@@ -3,8 +3,11 @@
package model
type UserLimits struct {
type ServerLimits struct {
MaxUsersLimit int64 `json:"maxUsersLimit"` // soft limit for max number of users.
MaxUsersHardLimit int64 `json:"maxUsersHardLimit"` // hard limit for max number of active users.
ActiveUserCount int64 `json:"activeUserCount"` // actual number of active users on server. Active = non deleted
MaxPostLimit int64 `json:"maxPostLimit"` // soft limit for max number of posts
PostCount int64 `json:"postCount"` // actual number of posts in system.
}