diff --git a/app/diagnostics.go b/app/diagnostics.go index 36545072eb..017013380f 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -802,6 +802,10 @@ func (a *App) trackServer() { data["system_admins"] = scr } + if scr, err := a.Srv().Store.GetDbVersion(); err == nil { + data["database_version"] = scr + } + a.SendDiagnostic(TRACK_SERVER, data) } diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go index 2519167873..78ee6771f5 100644 --- a/store/sqlstore/store.go +++ b/store/sqlstore/store.go @@ -45,6 +45,7 @@ type SqlStore interface { GetMaster() *gorp.DbMap GetSearchReplica() *gorp.DbMap GetReplica() *gorp.DbMap + GetDbVersion() (string, error) TotalMasterDbConnections() int TotalReadDbConnections() int TotalSearchDbConnections() int diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go index 3c4cb1cfd3..743a6aa464 100644 --- a/store/sqlstore/supplier.go +++ b/store/sqlstore/supplier.go @@ -301,6 +301,27 @@ func (ss *SqlSupplier) GetCurrentSchemaVersion() string { return version } +func (ss *SqlSupplier) GetDbVersion() (string, error) { + var sqlVersion string + if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { + sqlVersion = `SHOW server_version` + } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { + sqlVersion = `SELECT version()` + } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { + sqlVersion = `SELECT sqlite_version()` + } else { + return "", errors.New("Not supported driver") + } + + version, err := ss.GetReplica().SelectStr(sqlVersion) + if err != nil { + return "", err + } + + return version, nil + +} + func (ss *SqlSupplier) GetMaster() *gorp.DbMap { return ss.master } diff --git a/store/sqlstore/supplier_test.go b/store/sqlstore/supplier_test.go index 7443c2260f..80a4df90c5 100644 --- a/store/sqlstore/supplier_test.go +++ b/store/sqlstore/supplier_test.go @@ -4,14 +4,17 @@ package sqlstore_test import ( + "regexp" "testing" "github.com/mattermost/gorp" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/store/sqlstore" + "github.com/mattermost/mattermost-server/v5/store/storetest" ) func TestGetReplica(t *testing.T) { @@ -73,24 +76,10 @@ func TestGetReplica(t *testing.T) { t.Run(testCase.Description, func(t *testing.T) { t.Parallel() - driverName := model.DATABASE_DRIVER_SQLITE - dataSource := ":memory:" - maxIdleConns := 1 - connMaxLifetimeMilliseconds := 3600000 - maxOpenConns := 1 - queryTimeout := 5 - - settings := model.SqlSettings{ - DriverName: &driverName, - DataSource: &dataSource, - MaxIdleConns: &maxIdleConns, - ConnMaxLifetimeMilliseconds: &connMaxLifetimeMilliseconds, - MaxOpenConns: &maxOpenConns, - QueryTimeout: &queryTimeout, - DataSourceReplicas: testCase.DataSourceReplicas, - DataSourceSearchReplicas: testCase.DataSourceSearchReplicas, - } - supplier := sqlstore.NewSqlSupplier(settings, nil) + settings := makeSqlSettings(model.DATABASE_DRIVER_SQLITE) + settings.DataSourceReplicas = testCase.DataSourceReplicas + settings.DataSourceSearchReplicas = testCase.DataSourceSearchReplicas + supplier := sqlstore.NewSqlSupplier(*settings, nil) replicas := make(map[*gorp.DbMap]bool) for i := 0; i < 5; i++ { @@ -142,6 +131,26 @@ func TestGetReplica(t *testing.T) { } } +func TestGetDbVersion(t *testing.T) { + testDrivers := []string{ + model.DATABASE_DRIVER_POSTGRES, + model.DATABASE_DRIVER_MYSQL, + model.DATABASE_DRIVER_SQLITE, + } + + for _, driver := range testDrivers { + t.Run("Should return db version for "+driver, func(t *testing.T) { + t.Parallel() + settings := makeSqlSettings(driver) + supplier := sqlstore.NewSqlSupplier(*settings, nil) + + version, err := supplier.GetDbVersion() + require.Nil(t, err) + require.Regexp(t, regexp.MustCompile(`\d+\.\d+(\.\d+)?`), version) + }) + } +} + func TestGetAllConns(t *testing.T) { t.Parallel() testCases := []struct { @@ -210,27 +219,43 @@ func TestGetAllConns(t *testing.T) { testCase := testCase t.Run(testCase.Description, func(t *testing.T) { t.Parallel() - - driverName := model.DATABASE_DRIVER_SQLITE - dataSource := ":memory:" - maxIdleConns := 1 - connMaxLifetimeMilliseconds := 3600000 - maxOpenConns := 1 - queryTimeout := 5 - - settings := model.SqlSettings{ - DriverName: &driverName, - DataSource: &dataSource, - MaxIdleConns: &maxIdleConns, - ConnMaxLifetimeMilliseconds: &connMaxLifetimeMilliseconds, - MaxOpenConns: &maxOpenConns, - QueryTimeout: &queryTimeout, - DataSourceReplicas: testCase.DataSourceReplicas, - DataSourceSearchReplicas: testCase.DataSourceSearchReplicas, - } - supplier := sqlstore.NewSqlSupplier(settings, nil) + settings := makeSqlSettings(model.DATABASE_DRIVER_SQLITE) + settings.DataSourceReplicas = testCase.DataSourceReplicas + settings.DataSourceSearchReplicas = testCase.DataSourceSearchReplicas + supplier := sqlstore.NewSqlSupplier(*settings, nil) assert.Len(t, supplier.GetAllConns(), testCase.ExpectedNumConnections) }) } } + +func makeSqlSettings(driver string) *model.SqlSettings { + switch driver { + case model.DATABASE_DRIVER_POSTGRES: + return storetest.MakeSqlSettings(driver) + case model.DATABASE_DRIVER_MYSQL: + return storetest.MakeSqlSettings(driver) + case model.DATABASE_DRIVER_SQLITE: + return makeSqliteSettings() + } + + return nil +} + +func makeSqliteSettings() *model.SqlSettings { + driverName := model.DATABASE_DRIVER_SQLITE + dataSource := ":memory:" + maxIdleConns := 1 + connMaxLifetimeMilliseconds := 3600000 + maxOpenConns := 1 + queryTimeout := 5 + + return &model.SqlSettings{ + DriverName: &driverName, + DataSource: &dataSource, + MaxIdleConns: &maxIdleConns, + ConnMaxLifetimeMilliseconds: &connMaxLifetimeMilliseconds, + MaxOpenConns: &maxOpenConns, + QueryTimeout: &queryTimeout, + } +} diff --git a/store/store.go b/store/store.go index 61d5062f16..7093c89db1 100644 --- a/store/store.go +++ b/store/store.go @@ -54,6 +54,7 @@ type Store interface { UnlockFromMaster() DropAllTables() GetCurrentSchemaVersion() string + GetDbVersion() (string, error) TotalMasterDbConnections() int TotalReadDbConnections() int TotalSearchDbConnections() int diff --git a/store/storetest/mocks/SqlStore.go b/store/storetest/mocks/SqlStore.go index d04bed710f..e80ea11416 100644 --- a/store/storetest/mocks/SqlStore.go +++ b/store/storetest/mocks/SqlStore.go @@ -379,6 +379,27 @@ func (_m *SqlStore) GetCurrentSchemaVersion() string { return r0 } +// GetDbVersion provides a mock function with given fields: +func (_m *SqlStore) GetDbVersion() (string, error) { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetMaster provides a mock function with given fields: func (_m *SqlStore) GetMaster() *gorp.DbMap { ret := _m.Called() diff --git a/store/storetest/mocks/Store.go b/store/storetest/mocks/Store.go index 9f287fd899..031464befe 100644 --- a/store/storetest/mocks/Store.go +++ b/store/storetest/mocks/Store.go @@ -232,6 +232,27 @@ func (_m *Store) GetCurrentSchemaVersion() string { return r0 } +// GetDbVersion provides a mock function with given fields: +func (_m *Store) GetDbVersion() (string, error) { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Group provides a mock function with given fields: func (_m *Store) Group() store.GroupStore { ret := _m.Called() diff --git a/store/storetest/store.go b/store/storetest/store.go index c4e7b599b2..bcb45faa22 100644 --- a/store/storetest/store.go +++ b/store/storetest/store.go @@ -87,6 +87,7 @@ func (s *Store) Close() { /* do nothing */ } func (s *Store) LockToMaster() { /* do nothing */ } func (s *Store) UnlockFromMaster() { /* do nothing */ } func (s *Store) DropAllTables() { /* do nothing */ } +func (s *Store) GetDbVersion() (string, error) { return "", nil } func (s *Store) TotalMasterDbConnections() int { return 1 } func (s *Store) TotalReadDbConnections() int { return 1 } func (s *Store) TotalSearchDbConnections() int { return 1 }