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"
|
"database/sql"
|
||||||
dbsql "database/sql"
|
dbsql "database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -20,10 +21,12 @@ import (
|
|||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
"github.com/mattermost/morph/models"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost/server/public/model"
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
"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/channels/store"
|
||||||
"github.com/mattermost/mattermost/server/v8/einterfaces"
|
"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))
|
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) {
|
func (ss *SqlStore) GetDBSchemaVersion() (int, error) {
|
||||||
var version int
|
var version int
|
||||||
if err := ss.GetMasterX().Get(&version, "SELECT Version FROM db_migrations ORDER BY Version DESC LIMIT 1"); err != nil {
|
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) {
|
func TestGetAppliedMigrations(t *testing.T) {
|
||||||
testDrivers := []string{
|
testDrivers := []string{
|
||||||
model.DatabaseDriverPostgres,
|
model.DatabaseDriverPostgres,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ type Store interface {
|
|||||||
DropAllTables()
|
DropAllTables()
|
||||||
RecycleDBConnections(d time.Duration)
|
RecycleDBConnections(d time.Duration)
|
||||||
GetDBSchemaVersion() (int, error)
|
GetDBSchemaVersion() (int, error)
|
||||||
|
GetLocalSchemaVersion() (int, error)
|
||||||
GetAppliedMigrations() ([]model.AppliedMigration, error)
|
GetAppliedMigrations() ([]model.AppliedMigration, error)
|
||||||
GetDbVersion(numerical bool) (string, error)
|
GetDbVersion(numerical bool) (string, error)
|
||||||
// GetInternalMasterDB allows access to the raw master DB
|
// GetInternalMasterDB allows access to the raw master DB
|
||||||
|
|||||||
@@ -346,6 +346,30 @@ func (_m *Store) GetInternalReplicaDB() *sql.DB {
|
|||||||
return r0
|
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:
|
// Group provides a mock function with given fields:
|
||||||
func (_m *Store) Group() store.GroupStore {
|
func (_m *Store) Group() store.GroupStore {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
|||||||
@@ -114,17 +114,18 @@ func (s *Store) PostAcknowledgement() store.PostAcknowledgementStore {
|
|||||||
func (s *Store) PostPersistentNotification() store.PostPersistentNotificationStore {
|
func (s *Store) PostPersistentNotification() store.PostPersistentNotificationStore {
|
||||||
return &s.PostPersistentNotificationStore
|
return &s.PostPersistentNotificationStore
|
||||||
}
|
}
|
||||||
func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ }
|
func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ }
|
||||||
func (s *Store) Close() { /* do nothing */ }
|
func (s *Store) Close() { /* do nothing */ }
|
||||||
func (s *Store) LockToMaster() { /* do nothing */ }
|
func (s *Store) LockToMaster() { /* do nothing */ }
|
||||||
func (s *Store) UnlockFromMaster() { /* do nothing */ }
|
func (s *Store) UnlockFromMaster() { /* do nothing */ }
|
||||||
func (s *Store) DropAllTables() { /* do nothing */ }
|
func (s *Store) DropAllTables() { /* do nothing */ }
|
||||||
func (s *Store) GetDbVersion(bool) (string, error) { return "", nil }
|
func (s *Store) GetDbVersion(bool) (string, error) { return "", nil }
|
||||||
func (s *Store) GetInternalMasterDB() *sql.DB { return nil }
|
func (s *Store) GetInternalMasterDB() *sql.DB { return nil }
|
||||||
func (s *Store) GetInternalReplicaDB() *sql.DB { return nil }
|
func (s *Store) GetInternalReplicaDB() *sql.DB { return nil }
|
||||||
func (s *Store) GetInternalReplicaDBs() []*sql.DB { return nil }
|
func (s *Store) GetInternalReplicaDBs() []*sql.DB { return nil }
|
||||||
func (s *Store) RecycleDBConnections(time.Duration) {}
|
func (s *Store) RecycleDBConnections(time.Duration) {}
|
||||||
func (s *Store) GetDBSchemaVersion() (int, error) { return 1, nil }
|
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) {
|
func (s *Store) GetAppliedMigrations() ([]model.AppliedMigration, error) {
|
||||||
return []model.AppliedMigration{}, nil
|
return []model.AppliedMigration{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
type ClusterInfo struct {
|
type ClusterInfo struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
ConfigHash string `json:"config_hash"`
|
SchemaVersion string `json:"schema_version"`
|
||||||
IPAddress string `json:"ipaddress"`
|
ConfigHash string `json:"config_hash"`
|
||||||
Hostname string `json:"hostname"`
|
IPAddress string `json:"ipaddress"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Props = {
|
|||||||
config_hash: string;
|
config_hash: string;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
ipaddress: string;
|
ipaddress: string;
|
||||||
|
schema_version: string;
|
||||||
}>;
|
}>;
|
||||||
reload: (e: MouseEvent<HTMLButtonElement>) => void;
|
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}>{versionMismatch} {clusterInfo.version}</td>
|
||||||
<td style={style.clusterCell}><div className='config-hash'>{configMismatch} {clusterInfo.config_hash}</div></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.ipaddress}</td>
|
||||||
|
<td style={style.clusterCell}>{clusterInfo.schema_version}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -185,6 +187,12 @@ export default class ClusterTable extends PureComponent<Props> {
|
|||||||
defaultMessage='Gossip Address'
|
defaultMessage='Gossip Address'
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.cluster.status_table.schema_version'
|
||||||
|
defaultMessage='DB Schema Version'
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -550,6 +550,7 @@
|
|||||||
"admin.cluster.status_table.config_hash": "Config File MD5",
|
"admin.cluster.status_table.config_hash": "Config File MD5",
|
||||||
"admin.cluster.status_table.hostname": "Hostname",
|
"admin.cluster.status_table.hostname": "Hostname",
|
||||||
"admin.cluster.status_table.reload": " Reload Cluster Status",
|
"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.status": "Status",
|
||||||
"admin.cluster.status_table.url": "Gossip Address",
|
"admin.cluster.status_table.url": "Gossip Address",
|
||||||
"admin.cluster.status_table.version": "Version",
|
"admin.cluster.status_table.version": "Version",
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export type ClusterInfo = {
|
|||||||
config_hash: string;
|
config_hash: string;
|
||||||
ipaddress: string;
|
ipaddress: string;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
|
schema_version: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AnalyticsRow = {
|
export type AnalyticsRow = {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user