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
```
Этот коммит содержится в:
Agniva De Sarker
2023-08-10 09:40:49 +05:30
коммит произвёл GitHub
родитель ace88288f0
Коммит 45a14e23a9
9 изменённых файлов: 110 добавлений и 16 удалений

Просмотреть файл

@@ -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 {

Просмотреть файл

@@ -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,

Просмотреть файл

@@ -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

Просмотреть файл

@@ -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()

Просмотреть файл

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

Просмотреть файл

@@ -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"`
}

Просмотреть файл

@@ -15,6 +15,7 @@ type Props = {
config_hash: string;
hostname: string;
ipaddress: string;
schema_version: string;
}>;
reload: (e: MouseEvent<HTMLButtonElement>) => void;
}
@@ -130,6 +131,7 @@ export default class ClusterTable extends PureComponent<Props> {
<td style={style.clusterCell}>{versionMismatch} {clusterInfo.version}</td>
<td style={style.clusterCell}><div className='config-hash'>{configMismatch} {clusterInfo.config_hash}</div></td>
<td style={style.clusterCell}>{clusterInfo.ipaddress}</td>
<td style={style.clusterCell}>{clusterInfo.schema_version}</td>
</tr>
);
});
@@ -185,6 +187,12 @@ export default class ClusterTable extends PureComponent<Props> {
defaultMessage='Gossip Address'
/>
</th>
<th>
<FormattedMessage
id='admin.cluster.status_table.schema_version'
defaultMessage='DB Schema Version'
/>
</th>
</tr>
</thead>
<tbody>

Просмотреть файл

@@ -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",

Просмотреть файл

@@ -70,6 +70,7 @@ export type ClusterInfo = {
config_hash: string;
ipaddress: string;
hostname: string;
schema_version: string;
};
export type AnalyticsRow = {