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 ```
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ace88288f0
Коммит
45a14e23a9
@@ -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 = {
|
||||
|
||||
Ссылка в новой задаче
Block a user