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) }