Fail vs. fatal on store startup (#24170)

If the store fails to initialize (e.g. run a migration), it would `log.Fatal` and then `os.Exit`. Unfortunately, this trips up `TestMain`, which happily keeps running tests, now guaranteed to fail.

Avoid this by instead returning an error from the store initialization, handling appropriately at the layer above.
This commit is contained in:
Jesse Hallam 2023-08-04 23:05:01 -03:00 committed by GitHub
parent c030bb44f5
commit e39b485c4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 98 additions and 41 deletions

View File

@ -204,7 +204,11 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) {
// Timer layer // Timer layer
// | // |
// Cache layer // Cache layer
ps.sqlStore = sqlstore.New(ps.Config().SqlSettings, ps.metricsIFace) var err error
ps.sqlStore, err = sqlstore.New(ps.Config().SqlSettings, ps.metricsIFace)
if err != nil {
return nil, err
}
searchStore := searchlayer.NewSearchLayer( searchStore := searchlayer.NewSearchLayer(
retrylayer.New(ps.sqlStore), retrylayer.New(ps.sqlStore),

View File

@ -31,7 +31,10 @@ func (p *MyPlugin) OnConfigurationChange() error {
func (p *MyPlugin) MessageWillBePosted(_ *plugin.Context, _ *model.Post) (*model.Post, string) { func (p *MyPlugin) MessageWillBePosted(_ *plugin.Context, _ *model.Post) (*model.Post, string) {
settings := p.API.GetUnsanitizedConfig().SqlSettings settings := p.API.GetUnsanitizedConfig().SqlSettings
settings.Trace = model.NewBool(false) settings.Trace = model.NewBool(false)
store := sqlstore.New(settings, nil) store, err := sqlstore.New(settings, nil)
if err != nil {
panic(err)
}
store.GetMasterX().Close() store.GetMasterX().Close()
for _, isMaster := range []bool{true, false} { for _, isMaster := range []bool{true, false} {

View File

@ -12,6 +12,7 @@ import (
"github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/channels/store/sqlstore" "github.com/mattermost/mattermost/server/v8/channels/store/sqlstore"
"github.com/mattermost/mattermost/server/v8/channels/store/storetest" "github.com/mattermost/mattermost/server/v8/channels/store/storetest"
"golang.org/x/sync/errgroup"
) )
type storeType struct { type storeType struct {
@ -91,23 +92,29 @@ func initStores() {
panic(err) panic(err)
} }
}() }()
var wg sync.WaitGroup var eg errgroup.Group
for _, st := range storeTypes { for _, st := range storeTypes {
st := st st := st
wg.Add(1) eg.Go(func() error {
go func() {
var err error var err error
defer wg.Done()
st.SqlStore = sqlstore.New(*st.SqlSettings, nil) st.SqlStore, err = sqlstore.New(*st.SqlSettings, nil)
if err != nil {
return err
}
st.Store, err = NewLocalCacheLayer(st.SqlStore, nil, nil, getMockCacheProvider()) st.Store, err = NewLocalCacheLayer(st.SqlStore, nil, nil, getMockCacheProvider())
if err != nil { if err != nil {
panic(err) return err
} }
st.Store.DropAllTables() st.Store.DropAllTables()
st.Store.MarkSystemRanUnitTests() st.Store.MarkSystemRanUnitTests()
}()
return nil
})
}
if err := eg.Wait(); err != nil {
panic(err)
} }
wg.Wait()
} }
var tearDownStoresOnce sync.Once var tearDownStoresOnce sync.Once

View File

@ -14,6 +14,7 @@ import (
"github.com/mattermost/mattermost/server/v8/channels/store/storetest" "github.com/mattermost/mattermost/server/v8/channels/store/storetest"
"github.com/mattermost/mattermost/server/v8/channels/testlib" "github.com/mattermost/mattermost/server/v8/channels/testlib"
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine" "github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
"github.com/stretchr/testify/require"
) )
// Test to verify race condition on UpdateConfig. The test must run with -race flag in order to verify // Test to verify race condition on UpdateConfig. The test must run with -race flag in order to verify
@ -24,7 +25,8 @@ func TestUpdateConfigRace(t *testing.T) {
driverName = model.DatabaseDriverPostgres driverName = model.DatabaseDriverPostgres
} }
settings := storetest.MakeSqlSettings(driverName, false) settings := storetest.MakeSqlSettings(driverName, false)
store := sqlstore.New(*settings, nil) store, err := sqlstore.New(*settings, nil)
require.NoError(t, err)
cfg := &model.Config{} cfg := &model.Config{}
cfg.SetDefaults() cfg.SetDefaults()

View File

@ -8,6 +8,7 @@ import (
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestUpAndDownMigrations(t *testing.T) { func TestUpAndDownMigrations(t *testing.T) {
@ -23,7 +24,8 @@ func TestUpAndDownMigrations(t *testing.T) {
t.Skip(err) t.Skip(err)
} }
store := New(*settings, nil) store, err := New(*settings, nil)
require.NoError(t, err)
defer store.Close() defer store.Close()
err = store.migrate(migrationsDirectionDown, false) err = store.migrate(migrationsDirectionDown, false)

View File

@ -135,7 +135,7 @@ type SqlStore struct {
wgMonitor *sync.WaitGroup wgMonitor *sync.WaitGroup
} }
func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlStore { func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) (*SqlStore, error) {
store := &SqlStore{ store := &SqlStore{
rrCounter: 0, rrCounter: 0,
srCounter: 0, srCounter: 0,
@ -147,7 +147,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
err := store.initConnection() err := store.initConnection()
if err != nil { if err != nil {
mlog.Fatal("Error setting up connections", mlog.Err(err)) return nil, errors.Wrap(err, "error setting up connections")
} }
store.wgMonitor.Add(1) store.wgMonitor.Add(1)
@ -155,32 +155,32 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
ver, err := store.GetDbVersion(true) ver, err := store.GetDbVersion(true)
if err != nil { if err != nil {
mlog.Fatal("Error while getting DB version.", mlog.Err(err)) return nil, errors.Wrap(err, "error while getting DB version")
} }
ok, err := store.ensureMinimumDBVersion(ver) ok, err := store.ensureMinimumDBVersion(ver)
if !ok { if !ok {
mlog.Fatal("Error while checking DB version.", mlog.Err(err)) return nil, errors.Wrap(err, "error while checking DB version")
} }
err = store.ensureDatabaseCollation() err = store.ensureDatabaseCollation()
if err != nil { if err != nil {
mlog.Fatal("Error while checking DB collation.", mlog.Err(err)) return nil, errors.Wrap(err, "error while checking DB collation")
} }
err = store.migrate(migrationsDirectionUp, false) err = store.migrate(migrationsDirectionUp, false)
if err != nil { if err != nil {
mlog.Fatal("Failed to apply database migrations.", mlog.Err(err)) return nil, errors.Wrap(err, "failed to apply database migrations")
} }
store.isBinaryParam, err = store.computeBinaryParam() store.isBinaryParam, err = store.computeBinaryParam()
if err != nil { if err != nil {
mlog.Fatal("Failed to compute binary param", mlog.Err(err)) return nil, errors.Wrap(err, "failed to compute binary param")
} }
store.pgDefaultTextSearchConfig, err = store.computeDefaultTextSearchConfig() store.pgDefaultTextSearchConfig, err = store.computeDefaultTextSearchConfig()
if err != nil { if err != nil {
mlog.Fatal("Failed to compute default text search config", mlog.Err(err)) return nil, errors.Wrap(err, "failed to compute default text search config")
} }
store.stores.team = newSqlTeamStore(store) store.stores.team = newSqlTeamStore(store)
@ -229,7 +229,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures() store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
return store return store, nil
} }
// SetupConnection sets up the connection to the database and pings it to make sure it's alive. // SetupConnection sets up the connection to the database and pings it to make sure it's alive.

View File

@ -20,6 +20,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock" "github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
@ -126,19 +127,26 @@ func initStores() {
panic(err) panic(err)
} }
}() }()
var wg sync.WaitGroup
var eg errgroup.Group
for _, st := range storeTypes { for _, st := range storeTypes {
st := st st := st
wg.Add(1) eg.Go(func() error {
go func() { var err error
defer wg.Done() st.SqlStore, err = New(*st.SqlSettings, nil)
st.SqlStore = New(*st.SqlSettings, nil) if err != nil {
return err
}
st.Store = st.SqlStore st.Store = st.SqlStore
st.Store.DropAllTables() st.Store.DropAllTables()
st.Store.MarkSystemRanUnitTests() st.Store.MarkSystemRanUnitTests()
}()
return nil
})
}
if err := eg.Wait(); err != nil {
panic(err)
} }
wg.Wait()
} }
var tearDownStoresOnce sync.Once var tearDownStoresOnce sync.Once
@ -175,7 +183,8 @@ func TestStoreLicenseRace(t *testing.T) {
t.Skip(err) t.Skip(err)
} }
store := New(*settings, nil) store, err := New(*settings, nil)
require.NoError(t, err)
defer func() { defer func() {
store.Close() store.Close()
storetest.CleanupSqlSettings(settings) storetest.CleanupSqlSettings(settings)
@ -275,7 +284,8 @@ func TestGetReplica(t *testing.T) {
settings.DataSourceReplicas = dataSourceReplicas settings.DataSourceReplicas = dataSourceReplicas
settings.DataSourceSearchReplicas = dataSourceSearchReplicas settings.DataSourceSearchReplicas = dataSourceSearchReplicas
store := New(*settings, nil) store, err := New(*settings, nil)
require.NoError(t, err)
defer func() { defer func() {
store.Close() store.Close()
storetest.CleanupSqlSettings(settings) storetest.CleanupSqlSettings(settings)
@ -348,7 +358,8 @@ func TestGetReplica(t *testing.T) {
settings.DataSourceReplicas = dataSourceReplicas settings.DataSourceReplicas = dataSourceReplicas
settings.DataSourceSearchReplicas = dataSourceSearchReplicas settings.DataSourceSearchReplicas = dataSourceSearchReplicas
store := New(*settings, nil) store, err := New(*settings, nil)
require.NoError(t, err)
defer func() { defer func() {
store.Close() store.Close()
storetest.CleanupSqlSettings(settings) storetest.CleanupSqlSettings(settings)
@ -417,7 +428,8 @@ func TestGetDbVersion(t *testing.T) {
t.Skip(err) t.Skip(err)
} }
store := New(*settings, nil) store, err := New(*settings, nil)
require.NoError(t, err)
version, err := store.GetDbVersion(false) version, err := store.GetDbVersion(false)
require.NoError(t, err) require.NoError(t, err)
@ -641,7 +653,8 @@ func TestGetAllConns(t *testing.T) {
settings.DataSourceReplicas = dataSourceReplicas settings.DataSourceReplicas = dataSourceReplicas
settings.DataSourceSearchReplicas = dataSourceSearchReplicas settings.DataSourceSearchReplicas = dataSourceSearchReplicas
store := New(*settings, nil) store, err := New(*settings, nil)
require.NoError(t, err)
defer func() { defer func() {
store.Close() store.Close()
storetest.CleanupSqlSettings(settings) storetest.CleanupSqlSettings(settings)
@ -853,7 +866,8 @@ func TestGetDBSchemaVersion(t *testing.T) {
if err != nil { if err != nil {
t.Skip(err) t.Skip(err)
} }
store := New(*settings, nil) store, err := New(*settings, nil)
require.NoError(t, err)
assetsList, err := assets.ReadDir(filepath.Join("migrations", driver)) assetsList, err := assets.ReadDir(filepath.Join("migrations", driver))
require.NoError(t, err) require.NoError(t, err)
@ -891,7 +905,8 @@ func TestGetAppliedMigrations(t *testing.T) {
if err != nil { if err != nil {
t.Skip(err) t.Skip(err)
} }
store := New(*settings, nil) store, err := New(*settings, nil)
require.NoError(t, err)
assetsList, err := assets.ReadDir(filepath.Join("migrations", driver)) assetsList, err := assets.ReadDir(filepath.Join("migrations", driver))
require.NoError(t, err) require.NoError(t, err)

View File

@ -118,7 +118,12 @@ func (h *MainHelper) setupStore(withReadReplica bool) {
h.SearchEngine = searchengine.NewBroker(config) h.SearchEngine = searchengine.NewBroker(config)
h.ClusterInterface = &FakeClusterInterface{} h.ClusterInterface = &FakeClusterInterface{}
h.SQLStore = sqlstore.New(*h.Settings, nil)
var err error
h.SQLStore, err = sqlstore.New(*h.Settings, nil)
if err != nil {
panic(err)
}
h.Store = searchlayer.NewSearchLayer(&TestStore{ h.Store = searchlayer.NewSearchLayer(&TestStore{
h.SQLStore, h.SQLStore,
}, h.SearchEngine, config) }, h.SearchEngine, config)
@ -130,7 +135,12 @@ func (h *MainHelper) ToggleReplicasOff() {
} }
h.Settings.DataSourceReplicas = []string{} h.Settings.DataSourceReplicas = []string{}
lic := h.SQLStore.GetLicense() lic := h.SQLStore.GetLicense()
h.SQLStore = sqlstore.New(*h.Settings, nil)
var err error
h.SQLStore, err = sqlstore.New(*h.Settings, nil)
if err != nil {
panic(err)
}
h.SQLStore.UpdateLicense(lic) h.SQLStore.UpdateLicense(lic)
} }
@ -140,7 +150,13 @@ func (h *MainHelper) ToggleReplicasOn() {
} }
h.Settings.DataSourceReplicas = h.replicas h.Settings.DataSourceReplicas = h.replicas
lic := h.SQLStore.GetLicense() lic := h.SQLStore.GetLicense()
h.SQLStore = sqlstore.New(*h.Settings, nil)
var err error
h.SQLStore, err = sqlstore.New(*h.Settings, nil)
if err != nil {
panic(err)
}
h.SQLStore.UpdateLicense(lic) h.SQLStore.UpdateLicense(lic)
} }

View File

@ -113,7 +113,10 @@ func initDbCmdF(command *cobra.Command, _ []string) error {
} }
defer configStore.Close() defer configStore.Close()
sqlStore := sqlstore.New(configStore.Get().SqlSettings, nil) sqlStore, err := sqlstore.New(configStore.Get().SqlSettings, nil)
if err != nil {
return errors.Wrap(err, "failed to initialize store")
}
defer sqlStore.Close() defer sqlStore.Close()
CommandPrettyPrintln("Database store correctly initialised") CommandPrettyPrintln("Database store correctly initialised")

View File

@ -64,5 +64,5 @@ func initStoreCommandContextCobra(command *cobra.Command) (store.Store, error) {
} }
config := cfgStore.Get() config := cfgStore.Get()
return sqlstore.New(config.SqlSettings, nil), nil return sqlstore.New(config.SqlSettings, nil)
} }

View File

@ -49,7 +49,12 @@ func (s *BleveEngineTestSuite) setupStore() {
driverName = model.DatabaseDriverPostgres driverName = model.DatabaseDriverPostgres
} }
s.SQLSettings = storetest.MakeSqlSettings(driverName, false) s.SQLSettings = storetest.MakeSqlSettings(driverName, false)
s.SQLStore = sqlstore.New(*s.SQLSettings, nil)
var err error
s.SQLStore, err = sqlstore.New(*s.SQLSettings, nil)
if err != nil {
s.Require().FailNow("Cannot initialize store: %s", err.Error())
}
cfg := &model.Config{} cfg := &model.Config{}
cfg.SetDefaults() cfg.SetDefaults()