* users: add cache to service

* reflect review comments
Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2021-06-14 18:08:00 +03:00
коммит произвёл GitHub
родитель 0ae307808a
Коммит 24fb0033f4
26 изменённых файлов: 364 добавлений и 271 удалений

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

@@ -8,12 +8,14 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sync"
"testing"
"github.com/mattermost/mattermost-server/v5/app/request"
"github.com/mattermost/mattermost-server/v5/config"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/services/cache"
"github.com/mattermost/mattermost-server/v5/store"
)
@@ -72,9 +74,22 @@ func setupTestHelper(s store.Store, includeCacheLayer bool, tb testing.TB) *Test
configStore.Set(config)
buffer := &bytes.Buffer{}
provider := cache.NewProvider()
cache, err := provider.NewCache(&cache.CacheOptions{
Size: model.SESSION_CACHE_SIZE,
Striped: true,
StripedBuckets: maxInt(runtime.NumCPU()-1, 1),
})
if err != nil {
panic(err)
}
return &TestHelper{
service: &UserService{store: s.User(), config: configStore.Get},
service: &UserService{
store: s.User(),
sessionStore: s.Session(),
sessionCache: cache,
config: configStore.Get,
},
Context: &request.Context{},
configStore: configStore,
dbStore: s,

100
services/users/session.go Обычный файл
Просмотреть файл

@@ -0,0 +1,100 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package users
import (
"time"
"github.com/mattermost/mattermost-server/v5/model"
)
func (us *UserService) ReturnSessionToPool(session *model.Session) {
if session != nil {
session.Id = ""
us.sessionPool.Put(session)
}
}
func (us *UserService) CreateSession(session *model.Session) (*model.Session, error) {
session.Token = ""
session, err := us.sessionStore.Save(session)
if err != nil {
return nil, err
}
us.AddSessionToCache(session)
return session, nil
}
func (us *UserService) GetSession(token string) (*model.Session, error) {
var session = us.sessionPool.Get().(*model.Session)
if err := us.sessionCache.Get(token, session); err == nil {
if us.metrics != nil {
us.metrics.IncrementMemCacheHitCounterSession()
}
} else {
if us.metrics != nil {
us.metrics.IncrementMemCacheMissCounterSession()
}
}
return session, nil
}
func (us *UserService) AddSessionToCache(session *model.Session) {
us.sessionCache.SetWithExpiry(session.Token, session, time.Duration(int64(*us.config().ServiceSettings.SessionCacheInMinutes))*time.Minute)
}
func (us *UserService) SessionCacheLength() int {
if l, err := us.sessionCache.Len(); err == nil {
return l
}
return 0
}
func (us *UserService) ClearUserSessionCacheLocal(userID string) {
if keys, err := us.sessionCache.Keys(); err == nil {
var session *model.Session
for _, key := range keys {
if err := us.sessionCache.Get(key, &session); err == nil {
if session.UserId == userID {
us.sessionCache.Remove(key)
if us.metrics != nil {
us.metrics.IncrementMemCacheInvalidationCounterSession()
}
}
}
}
}
}
func (us *UserService) ClearAllUsersSessionCacheLocal() {
us.sessionCache.Purge()
}
func (us *UserService) ClearUserSessionCache(userID string) {
us.ClearUserSessionCacheLocal(userID)
if us.cluster != nil {
msg := &model.ClusterMessage{
Event: model.CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_USER,
SendType: model.CLUSTER_SEND_RELIABLE,
Data: userID,
}
us.cluster.SendClusterMessage(msg)
}
}
func (us *UserService) ClearAllUsersSessionCache() {
us.ClearAllUsersSessionCacheLocal()
if us.cluster != nil {
msg := &model.ClusterMessage{
Event: model.CLUSTER_EVENT_CLEAR_SESSION_CACHE_FOR_ALL_USERS,
SendType: model.CLUSTER_SEND_RELIABLE,
}
us.cluster.SendClusterMessage(msg)
}
}

49
services/users/session_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,49 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package users
import (
"testing"
"time"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/stretchr/testify/require"
)
func TestCache(t *testing.T) {
th := Setup(t)
defer th.TearDown()
session := &model.Session{
Id: model.NewId(),
Token: model.NewId(),
UserId: model.NewId(),
}
session2 := &model.Session{
Id: model.NewId(),
Token: model.NewId(),
UserId: model.NewId(),
}
th.service.sessionCache.SetWithExpiry(session.Token, session, 5*time.Minute)
th.service.sessionCache.SetWithExpiry(session2.Token, session2, 5*time.Minute)
keys, err := th.service.sessionCache.Keys()
require.NoError(t, err)
require.NotEmpty(t, keys)
th.service.ClearUserSessionCache(session.UserId)
rkeys, err := th.service.sessionCache.Keys()
require.NoError(t, err)
require.Lenf(t, rkeys, len(keys)-1, "should have one less: %d - %d != 1", len(keys), len(rkeys))
require.NotEmpty(t, rkeys)
th.service.ClearAllUsersSessionCache()
rkeys, err = th.service.sessionCache.Keys()
require.NoError(t, err)
require.Empty(t, rkeys)
}

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

@@ -5,17 +5,27 @@ package users
import (
"context"
"errors"
"fmt"
"runtime"
"sync"
"github.com/mattermost/mattermost-server/v5/einterfaces"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/services/cache"
"github.com/mattermost/mattermost-server/v5/shared/i18n"
"github.com/mattermost/mattermost-server/v5/shared/mlog"
"github.com/mattermost/mattermost-server/v5/store"
)
type UserService struct {
store store.UserStore
config func() *model.Config
store store.UserStore
sessionStore store.SessionStore
sessionCache cache.Cache
sessionPool sync.Pool
metrics einterfaces.MetricsInterface
cluster einterfaces.ClusterInterface
config func() *model.Config
}
type UserCreateOptions struct {
@@ -23,11 +33,49 @@ type UserCreateOptions struct {
FromImport bool
}
func New(s store.UserStore, cfgFn func() *model.Config) *UserService {
return &UserService{
store: s,
config: cfgFn,
// ServiceInitializer is used to initialize the UserService.
type ServiceInitializer struct {
// Mandatory fields
UserStore store.UserStore
SessionStore store.SessionStore
ConfigFn func() *model.Config
// Optional fields
Metrics einterfaces.MetricsInterface
Cluster einterfaces.ClusterInterface
}
func New(initializer ServiceInitializer) (*UserService, error) {
cacheProvider := cache.NewProvider()
if err := cacheProvider.Connect(); err != nil {
return nil, fmt.Errorf("could not create cache provider: %w", err)
}
sessionCache, err := cacheProvider.NewCache(&cache.CacheOptions{
Size: model.SESSION_CACHE_SIZE,
Striped: true,
StripedBuckets: maxInt(runtime.NumCPU()-1, 1),
})
if err != nil {
return nil, fmt.Errorf("could not create session cache: %w", err)
}
if initializer.ConfigFn == nil || initializer.UserStore == nil || initializer.SessionStore == nil {
return nil, errors.New("required parameters are not provided")
}
return &UserService{
store: initializer.UserStore,
sessionStore: initializer.SessionStore,
config: initializer.ConfigFn,
metrics: initializer.Metrics,
cluster: initializer.Cluster,
sessionCache: sessionCache,
sessionPool: sync.Pool{
New: func() interface{} {
return &model.Session{}
},
},
}, nil
}
// CreateUser creates a user
@@ -41,11 +89,11 @@ func (us *UserService) CreateUser(user *model.User, opts UserCreateOptions) (*mo
user.Roles = model.SYSTEM_GUEST_ROLE_ID
}
if !user.IsLDAPUser() && !user.IsSAMLUser() && !user.IsGuest() && !checkUserDomain(user, *us.config().TeamSettings.RestrictCreationToDomains) {
if !user.IsLDAPUser() && !user.IsSAMLUser() && !user.IsGuest() && !CheckUserDomain(user, *us.config().TeamSettings.RestrictCreationToDomains) {
return nil, AcceptedDomainError
}
if !user.IsLDAPUser() && !user.IsSAMLUser() && user.IsGuest() && !checkUserDomain(user, *us.config().GuestAccountsSettings.RestrictCreationToDomains) {
if !user.IsLDAPUser() && !user.IsSAMLUser() && user.IsGuest() && !CheckUserDomain(user, *us.config().GuestAccountsSettings.RestrictCreationToDomains) {
return nil, AcceptedDomainError
}
@@ -194,3 +242,17 @@ func (us *UserService) GetUsersWithoutTeam(options *model.UserGetOptions) ([]*mo
return users, nil
}
func (us *UserService) InvalidateCacheForUser(userID string) {
us.store.InvalidateProfilesInChannelCacheByUser(userID)
us.store.InvalidateProfileCacheForUser(userID)
if us.cluster != nil {
msg := &model.ClusterMessage{
Event: model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_USER,
SendType: model.CLUSTER_SEND_BEST_EFFORT,
Data: userID,
}
us.cluster.SendClusterMessage(msg)
}
}

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

@@ -9,13 +9,38 @@ import (
"github.com/mattermost/mattermost-server/v5/model"
)
// checkUserDomain checks that a user's email domain matches a list of space-delimited domains as a string.
func checkUserDomain(user *model.User, domains string) bool {
return checkEmailDomain(user.Email, domains)
func maxInt(a, b int) int {
if a > b {
return a
}
return b
}
// checkEmailDomain checks that an email domain matches a list of space-delimited domains as a string.
func checkEmailDomain(email string, domains string) bool {
func (us *UserService) IsFirstUserAccount() bool {
cachedSessions, err := us.sessionCache.Len()
if err != nil {
return false
}
if cachedSessions == 0 {
count, err := us.store.Count(model.UserCountOptions{IncludeDeleted: true})
if err != nil {
return false
}
if count <= 0 {
return true
}
}
return false
}
// CheckUserDomain checks that a user's email domain matches a list of space-delimited domains as a string.
func CheckUserDomain(user *model.User, domains string) bool {
return CheckEmailDomain(user.Email, domains)
}
// CheckEmailDomain checks that an email domain matches a list of space-delimited domains as a string.
func CheckEmailDomain(email string, domains string) bool {
if domains == "" {
return true
}