From 8d1c42bc915d52e4dddf70e4945d153536f62861 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Fri, 15 Nov 2024 13:27:23 +0530 Subject: [PATCH] MM-61229: Place Redis behind enterprise (#28917) And re-order the server initialization (AGAIN). For the nth time, we found several bugs in the initialization process. 1. filestore.NewExportFileBackend and filestore.NewFileBackend depended on license, but the license wasn't even loaded until later! 2. The `ps.sqlStore.UpdateLicense` call also didn't work because the license wouldn't get loaded. It only accidentally worked because of `ps.AddLicenseListener` which would update the license later on. We remove that. Ideally, we would have loaded the license first and then checked for redis client, but it's very difficult to do that. Reasons are explained in the code comment. So we just wait until the license is loaded, and simply throw an error later. https://mattermost.atlassian.net/browse/MM-61229 ```release-note NONE ``` --------- Co-authored-by: Mattermost Build --- server/channels/api4/apitestlib.go | 1 + server/channels/app/options.go | 7 +++++ server/channels/app/platform/options.go | 7 +++++ server/channels/app/platform/service.go | 39 ++++++++++++++++--------- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/server/channels/api4/apitestlib.go b/server/channels/api4/apitestlib.go index f42eb506d1..2588d12e93 100644 --- a/server/channels/api4/apitestlib.go +++ b/server/channels/api4/apitestlib.go @@ -112,6 +112,7 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent *memoryConfig.CacheSettings.RedisAddress = redisHost + ":6379" *memoryConfig.CacheSettings.DisableClientCache = true *memoryConfig.CacheSettings.RedisDB = 0 + options = append(options, app.ForceEnableRedis()) } if updateConfig != nil { updateConfig(memoryConfig) diff --git a/server/channels/app/options.go b/server/channels/app/options.go index 7617e8b4e0..51ef683233 100644 --- a/server/channels/app/options.go +++ b/server/channels/app/options.go @@ -59,6 +59,13 @@ func SetFileStore(filestore filestore.FileBackend) Option { } } +func ForceEnableRedis() Option { + return func(s *Server) error { + s.platformOptions = append(s.platformOptions, platform.ForceEnableRedis()) + return nil + } +} + func RunEssentialJobs(s *Server) error { s.runEssentialJobs = true diff --git a/server/channels/app/platform/options.go b/server/channels/app/platform/options.go index 76a134f3f6..b5e46c1c9b 100644 --- a/server/channels/app/platform/options.go +++ b/server/channels/app/platform/options.go @@ -119,3 +119,10 @@ func SetCluster(cluster einterfaces.ClusterInterface) Option { return nil } } + +func ForceEnableRedis() Option { + return func(ps *PlatformService) error { + ps.forceEnableRedis = true + return nil + } +} diff --git a/server/channels/app/platform/service.go b/server/channels/app/platform/service.go index b61d77417b..7f02e213aa 100644 --- a/server/channels/app/platform/service.go +++ b/server/channels/app/platform/service.go @@ -103,6 +103,10 @@ type PlatformService struct { sharedChannelService SharedChannelServiceIFace pluginEnv HookRunner + + // This is a test mode setting used to enable Redis + // without a license. + forceEnableRedis bool } type HookRunner interface { @@ -257,8 +261,26 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) { } } + ps.Store, err = ps.newStore() + if err != nil { + return nil, fmt.Errorf("cannot create store: %w", err) + } + + // Step 7: Init License + if model.BuildEnterpriseReady == "true" { + ps.LoadLicense() + } license := ps.License() - // Step 3: Initialize filestore + + // This is a hack because ideally we wouldn't even have started the Redis client + // if the license didn't have clustering. But there's an intricate deadlock + // where license cannot be loaded before store, and store cannot be loaded before + // cache. So loading license before loading cache is an uphill battle. + if (license == nil || !*license.Features.Cluster) && *cacheConfig.CacheType == model.CacheTypeRedis && !ps.forceEnableRedis { + return nil, fmt.Errorf("Redis cannot be used in an instance without a license or a license without clustering") + } + + // Step 8: Initialize filestore if ps.filestore == nil { insecure := ps.Config().ServiceSettings.EnableInsecureOutgoingConnections backend, err2 := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(&ps.Config().FileSettings, license != nil && *license.Features.Compliance, insecure != nil && *insecure)) @@ -282,17 +304,11 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) { } } - ps.Store, err = ps.newStore() - if err != nil { - return nil, fmt.Errorf("cannot create store: %w", err) - } - // Note: we hardcode the session and status cache to LRU because they lead // to a lot of SCAN calls in case of Redis. We could potentially have a // reverse mapping to avoid the scan, but this needs more complicated code. // Leaving this for now. - // Needed before loading license ps.statusCache, err = cache.NewProvider().NewCache(&cache.CacheOptions{ Name: "Status", Size: model.StatusCacheSize, @@ -314,12 +330,7 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) { return nil, fmt.Errorf("could not create session cache: %w", err) } - // Step 7: Init License - if model.BuildEnterpriseReady == "true" { - ps.LoadLicense() - } - - // Step 8: Init Metrics Server depends on step 6 (store) and 7 (license) + // Step 9: Init Metrics Server depends on step 6 (store) and 7 (license) if ps.startMetrics { if mErr := ps.resetMetrics(); mErr != nil { return nil, mErr @@ -334,7 +345,7 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) { }) } - // Step 9: Init AsymmetricSigningKey depends on step 6 (store) + // Step 10: Init AsymmetricSigningKey depends on step 6 (store) if err = ps.EnsureAsymmetricSigningKey(); err != nil { return nil, fmt.Errorf("unable to ensure asymmetric signing key: %w", err) }