From 45a14e23a951cac666bd639805b77e6b560af1b3 Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Thu, 10 Aug 2023 09:40:49 +0530 Subject: [PATCH] MM-53228: Adding schema version to cluster info (#24209) With the schema version available, a job can query for the cluster info to confirm whether or not all nodes in a cluster are upgraded to the same version or not. This will help it in determining whether to start the job or not. https://mattermost.atlassian.net/browse/MM-53228 ```release-note NONE ``` --- server/channels/store/sqlstore/store.go | 31 +++++++++++++++++++ server/channels/store/sqlstore/store_test.go | 26 ++++++++++++++++ server/channels/store/store.go | 1 + .../channels/store/storetest/mocks/Store.go | 24 ++++++++++++++ server/channels/store/storetest/store.go | 23 +++++++------- server/public/model/cluster_info.go | 11 ++++--- .../admin_console/cluster_table.tsx | 8 +++++ webapp/channels/src/i18n/en.json | 1 + webapp/platform/types/src/admin.ts | 1 + 9 files changed, 110 insertions(+), 16 deletions(-) diff --git a/server/channels/store/sqlstore/store.go b/server/channels/store/sqlstore/store.go index 6c1a465945..200c34fa2c 100644 --- a/server/channels/store/sqlstore/store.go +++ b/server/channels/store/sqlstore/store.go @@ -8,6 +8,7 @@ import ( "database/sql" dbsql "database/sql" "fmt" + "path" "strconv" "strings" "sync" @@ -20,10 +21,12 @@ import ( _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/jmoiron/sqlx" "github.com/lib/pq" + "github.com/mattermost/morph/models" "github.com/pkg/errors" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" + "github.com/mattermost/mattermost/server/v8/channels/db" "github.com/mattermost/mattermost/server/v8/channels/store" "github.com/mattermost/mattermost/server/v8/einterfaces" ) @@ -1289,6 +1292,34 @@ func (ss *SqlStore) toReserveCase(str string) string { return fmt.Sprintf("`%s`", strings.Title(str)) } +func (ss *SqlStore) GetLocalSchemaVersion() (int, error) { + assets := db.Assets() + + assetsList, err := assets.ReadDir(path.Join("migrations", ss.DriverName())) + if err != nil { + return 0, err + } + + maxVersion := 0 + for _, entry := range assetsList { + // parse the version name from the file name + m := models.Regex.FindStringSubmatch(entry.Name()) + if len(m) < 2 { + return 0, fmt.Errorf("migration file name incorrectly formed: %s", entry.Name()) + } + + version, err := strconv.Atoi(m[1]) + if err != nil { + return 0, err + } + // store the highest version + if maxVersion < version { + maxVersion = version + } + } + return maxVersion, nil +} + func (ss *SqlStore) GetDBSchemaVersion() (int, error) { var version int if err := ss.GetMasterX().Get(&version, "SELECT Version FROM db_migrations ORDER BY Version DESC LIMIT 1"); err != nil { diff --git a/server/channels/store/sqlstore/store_test.go b/server/channels/store/sqlstore/store_test.go index 0ccc001dd7..e27af3da54 100644 --- a/server/channels/store/sqlstore/store_test.go +++ b/server/channels/store/sqlstore/store_test.go @@ -889,6 +889,32 @@ func TestGetDBSchemaVersion(t *testing.T) { } } +func TestGetLocalSchemaVersion(t *testing.T) { + testDrivers := []string{ + model.DatabaseDriverPostgres, + model.DatabaseDriverMysql, + } + + for _, d := range testDrivers { + driver := d + t.Run(driver, func(t *testing.T) { + settings, err := makeSqlSettings(driver) + if err != nil { + t.Skip(err) + } + store, err := New(*settings, nil) + require.NoError(t, err) + + ver, err := store.GetLocalSchemaVersion() + require.NoError(t, err) + + dbVer, err := store.GetDBSchemaVersion() + require.NoError(t, err) + require.Equal(t, ver, dbVer) + }) + } +} + func TestGetAppliedMigrations(t *testing.T) { testDrivers := []string{ model.DatabaseDriverPostgres, diff --git a/server/channels/store/store.go b/server/channels/store/store.go index a03469b867..d6611e6f23 100644 --- a/server/channels/store/store.go +++ b/server/channels/store/store.go @@ -67,6 +67,7 @@ type Store interface { DropAllTables() RecycleDBConnections(d time.Duration) GetDBSchemaVersion() (int, error) + GetLocalSchemaVersion() (int, error) GetAppliedMigrations() ([]model.AppliedMigration, error) GetDbVersion(numerical bool) (string, error) // GetInternalMasterDB allows access to the raw master DB diff --git a/server/channels/store/storetest/mocks/Store.go b/server/channels/store/storetest/mocks/Store.go index 2a50f246e0..bc8f8c3031 100644 --- a/server/channels/store/storetest/mocks/Store.go +++ b/server/channels/store/storetest/mocks/Store.go @@ -346,6 +346,30 @@ func (_m *Store) GetInternalReplicaDB() *sql.DB { return r0 } +// GetLocalSchemaVersion provides a mock function with given fields: +func (_m *Store) GetLocalSchemaVersion() (int, error) { + ret := _m.Called() + + var r0 int + var r1 error + if rf, ok := ret.Get(0).(func() (int, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + 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/server/channels/store/storetest/store.go b/server/channels/store/storetest/store.go index 2aa1a4f5d0..80fa81159a 100644 --- a/server/channels/store/storetest/store.go +++ b/server/channels/store/storetest/store.go @@ -114,17 +114,18 @@ func (s *Store) PostAcknowledgement() store.PostAcknowledgementStore { func (s *Store) PostPersistentNotification() store.PostPersistentNotificationStore { return &s.PostPersistentNotificationStore } -func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ } -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(bool) (string, error) { return "", nil } -func (s *Store) GetInternalMasterDB() *sql.DB { return nil } -func (s *Store) GetInternalReplicaDB() *sql.DB { return nil } -func (s *Store) GetInternalReplicaDBs() []*sql.DB { return nil } -func (s *Store) RecycleDBConnections(time.Duration) {} -func (s *Store) GetDBSchemaVersion() (int, error) { return 1, nil } +func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ } +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(bool) (string, error) { return "", nil } +func (s *Store) GetInternalMasterDB() *sql.DB { return nil } +func (s *Store) GetInternalReplicaDB() *sql.DB { return nil } +func (s *Store) GetInternalReplicaDBs() []*sql.DB { return nil } +func (s *Store) RecycleDBConnections(time.Duration) {} +func (s *Store) GetDBSchemaVersion() (int, error) { return 1, nil } +func (s *Store) GetLocalSchemaVersion() (int, error) { return 1, nil } func (s *Store) GetAppliedMigrations() ([]model.AppliedMigration, error) { return []model.AppliedMigration{}, nil } diff --git a/server/public/model/cluster_info.go b/server/public/model/cluster_info.go index 48d11d2fb1..e69401bc57 100644 --- a/server/public/model/cluster_info.go +++ b/server/public/model/cluster_info.go @@ -4,9 +4,10 @@ package model type ClusterInfo struct { - Id string `json:"id"` - Version string `json:"version"` - ConfigHash string `json:"config_hash"` - IPAddress string `json:"ipaddress"` - Hostname string `json:"hostname"` + Id string `json:"id"` + Version string `json:"version"` + SchemaVersion string `json:"schema_version"` + ConfigHash string `json:"config_hash"` + IPAddress string `json:"ipaddress"` + Hostname string `json:"hostname"` } diff --git a/webapp/channels/src/components/admin_console/cluster_table.tsx b/webapp/channels/src/components/admin_console/cluster_table.tsx index d97252dc4a..96367e6953 100644 --- a/webapp/channels/src/components/admin_console/cluster_table.tsx +++ b/webapp/channels/src/components/admin_console/cluster_table.tsx @@ -15,6 +15,7 @@ type Props = { config_hash: string; hostname: string; ipaddress: string; + schema_version: string; }>; reload: (e: MouseEvent) => void; } @@ -130,6 +131,7 @@ export default class ClusterTable extends PureComponent { {versionMismatch} {clusterInfo.version}
{configMismatch} {clusterInfo.config_hash}
{clusterInfo.ipaddress} + {clusterInfo.schema_version} ); }); @@ -185,6 +187,12 @@ export default class ClusterTable extends PureComponent { defaultMessage='Gossip Address' /> + + + diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 884a4f3498..e55272ee23 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -550,6 +550,7 @@ "admin.cluster.status_table.config_hash": "Config File MD5", "admin.cluster.status_table.hostname": "Hostname", "admin.cluster.status_table.reload": " Reload Cluster Status", + "admin.cluster.status_table.schema_version": "DB Schema Version", "admin.cluster.status_table.status": "Status", "admin.cluster.status_table.url": "Gossip Address", "admin.cluster.status_table.version": "Version", diff --git a/webapp/platform/types/src/admin.ts b/webapp/platform/types/src/admin.ts index 2e15030fad..caf19c5566 100644 --- a/webapp/platform/types/src/admin.ts +++ b/webapp/platform/types/src/admin.ts @@ -70,6 +70,7 @@ export type ClusterInfo = { config_hash: string; ipaddress: string; hostname: string; + schema_version: string; }; export type AnalyticsRow = {