[MM-41576] Add schema version to the client configuration (#19757)

* revamp db version and add applied migrations endpoint

* replace old schema version with new

* add db version subcommand

* add to local api

* reflect review comments

* log errors

* remove setting the version from model.CurrentVersion

* fix a test

* use different field for schema version

* add build hash and current version to the support packet

* add tests

* update test to use new assets
Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2022-03-15 16:39:23 +03:00
коммит произвёл GitHub
родитель 47c44a9b7d
Коммит b03d87af40
35 изменённых файлов: 399 добавлений и 50 удалений

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

@@ -70,6 +70,7 @@ func (api *API) InitSystem() {
api.BaseRoutes.System.Handle("/support_packet", api.APISessionRequired(generateSupportPacket)).Methods("GET")
api.BaseRoutes.System.Handle("/onboarding/complete", api.APISessionRequired(getOnboarding)).Methods("GET")
api.BaseRoutes.System.Handle("/onboarding/complete", api.APISessionRequired(completeOnboarding)).Methods("POST")
api.BaseRoutes.System.Handle("/schema/version", api.APISessionRequired(getAppliedSchemaMigrations)).Methods("GET")
}
func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -934,3 +935,28 @@ func completeOnboarding(c *Context, w http.ResponseWriter, r *http.Request) {
auditRec.Success()
ReturnStatusOK(w)
}
func getAppliedSchemaMigrations(c *Context, w http.ResponseWriter, r *http.Request) {
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
c.Err = model.NewAppError("getAppliedMigrations", "app.system.applied_migrations.not_authorized", nil, "", http.StatusForbidden)
return
}
auditRec := c.MakeAuditRecord("getAppliedSchemaMigrations", audit.Fail)
defer c.LogAuditRec(auditRec)
migrations, appErr := c.App.GetAppliedSchemaMigrations()
if appErr != nil {
c.Err = appErr
return
}
js, jsonErr := json.Marshal(migrations)
if jsonErr != nil {
c.Err = model.NewAppError("getAppliedMigrations", "api.marshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
return
}
w.Write(js)
auditRec.Success()
}

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

@@ -18,6 +18,7 @@ func (api *API) InitSystemLocal() {
api.BaseRoutes.APIRoot.Handle("/server_busy", api.APILocal(getServerBusyExpires)).Methods("GET")
api.BaseRoutes.APIRoot.Handle("/server_busy", api.APILocal(clearServerBusy)).Methods("DELETE")
api.BaseRoutes.APIRoot.Handle("/integrity", api.APILocal(localCheckIntegrity)).Methods("POST")
api.BaseRoutes.System.Handle("/schema/version", api.APILocal(getAppliedSchemaMigrations)).Methods("GET")
}
func localCheckIntegrity(c *Context, w http.ResponseWriter, r *http.Request) {

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

@@ -931,3 +931,20 @@ func TestCompleteOnboarding(t *testing.T) {
CheckOKStatus(t, resp)
})
}
func TestGetAppliedSchemaMigrations(t *testing.T) {
th := Setup(t)
defer th.TearDown()
t.Run("as a regular user", func(t *testing.T) {
_, resp, err := th.Client.GetAppliedSchemaMigrations()
require.Error(t, err)
CheckForbiddenStatus(t, resp)
})
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
_, resp, err := c.GetAppliedSchemaMigrations()
require.NoError(t, err)
CheckOKStatus(t, resp)
})
}

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

@@ -550,6 +550,7 @@ type AppIface interface {
GetAllTeamsPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, *model.AppError)
GetAllTeamsPageWithCount(offset int, limit int, opts *model.TeamSearch) (*model.TeamsWithCount, *model.AppError)
GetAnalytics(name string, teamID string) (model.AnalyticsRows, *model.AppError)
GetAppliedSchemaMigrations() ([]model.AppliedMigration, *model.AppError)
GetAudits(userID string, limit int) (model.Audits, *model.AppError)
GetAuditsPage(userID string, page int, perPage int) (model.Audits, *model.AppError)
GetAuthorizationCode(w http.ResponseWriter, r *http.Request, service string, props map[string]string, loginHint string) (string, *model.AppError)

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

@@ -56,6 +56,7 @@ func TestUnitUpdateConfig(t *testing.T) {
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("License").Return(&mockLicenseStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
prev := *th.App.Config().ServiceSettings.SiteURL

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

@@ -2103,6 +2103,7 @@ func TestMarkChannelAsUnreadFromPostPanic(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("License").Return(&mockLicenseStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ThreadAutoFollow = true
@@ -2132,6 +2133,7 @@ func TestClearChannelMembersCache(t *testing.T) {
ChannelId: "1",
}}, nil)
mockStore.On("Channel").Return(&mockChannelStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.ClearChannelMembersCache("channelID")
}
@@ -2152,6 +2154,7 @@ func TestGetMemberCountsByGroup(t *testing.T) {
}
mockChannelStore.On("GetMemberCountsByGroup", context.Background(), "channelID", true).Return(cmc, nil)
mockStore.On("Channel").Return(&mockChannelStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
resp, err := th.App.GetMemberCountsByGroup(context.Background(), "channelID", true)
require.Nil(t, err)
require.ElementsMatch(t, cmc, resp)

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

@@ -420,6 +420,11 @@ func (a *App) ClientConfigWithComputed() map[string]string {
if installationDate, err := a.ch.srv.getSystemInstallDate(); err == nil {
respCfg["InstallationDate"] = strconv.FormatInt(installationDate, 10)
}
if ver, err := a.ch.srv.Store.GetDBSchemaVersion(); err != nil {
mlog.Error("Could not get the schema version", mlog.Err(err))
} else {
respCfg["SchemaVersion"] = strconv.Itoa(ver)
}
return respCfg
}

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

@@ -79,12 +79,16 @@ func TestClientConfigWithComputed(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
config := th.App.ClientConfigWithComputed()
_, ok := config["NoAccounts"]
assert.True(t, ok, "expected NoAccounts in returned config")
_, ok = config["MaxPostSize"]
assert.True(t, ok, "expected MaxPostSize in returned config")
v, ok := config["SchemaVersion"]
assert.True(t, ok, "expected SchemaVersion in returned config")
assert.Equal(t, "1", v)
}
func TestEnsureInstallationDate(t *testing.T) {

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

@@ -77,6 +77,7 @@ func TestSAMLSettings(t *testing.T) {
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)
mockSystemStore.On("GetByName", "FirstServerRunTimestamp").Return(&model.System{Name: "FirstServerRunTimestamp", Value: "10"}, nil)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)

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

@@ -567,6 +567,7 @@ func TestGetPushNotificationMessage(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
for name, tc := range map[string]struct {
Message string
@@ -1149,6 +1150,7 @@ func TestClearPushNotificationSync(t *testing.T) {
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("Session").Return(&mockSessionStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
@@ -1222,6 +1224,7 @@ func TestUpdateMobileAppBadgeSync(t *testing.T) {
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("Session").Return(&mockSessionStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
@@ -1288,6 +1291,7 @@ func TestSendAckToPushProxy(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.EmailSettings.PushNotificationServer = pushServer.URL
@@ -1531,6 +1535,7 @@ func BenchmarkPushNotificationThroughput(b *testing.B) {
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("Session").Return(&mockSessionStore)
mockStore.On("Preference").Return(&mockPreferenceStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
// create 50 users, each having 2 sessions.
type userSession struct {

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

@@ -4604,6 +4604,28 @@ func (a *OpenTracingAppLayer) GetAnalytics(name string, teamID string) (model.An
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetAppliedSchemaMigrations() ([]model.AppliedMigration, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAppliedSchemaMigrations")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
defer func() {
a.app.Srv().Store.SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1 := a.app.GetAppliedSchemaMigrations()
if resultVar1 != nil {
span.LogFields(spanlog.Error(resultVar1))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetAudits(userID string, limit int) (model.Audits, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetAudits")

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

@@ -34,6 +34,7 @@ func TestPluginPublicKeys(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
path, _ := fileutils.FindDir("tests")
publicKeyFilename := "test-public-key.plugin.gpg"

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

@@ -464,6 +464,7 @@ func TestImageProxy(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.SiteURL = "http://mymattermost.com"

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

@@ -32,6 +32,7 @@ func TestNoticeValidation(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("Preference").Return(&mockPreferenceStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
mockSystemStore.On("SaveOrUpdate", &model.System{Name: "ActiveLicenseId", Value: ""}).Return(nil)
mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil)
mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil)

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

@@ -20,6 +20,7 @@ import (
"os/exec"
"path"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
@@ -752,10 +753,10 @@ func (s *Server) Channels() *Channels {
return ch
}
// Return Database type (postgres or mysql) and current version of Mattermost
func (s *Server) DatabaseTypeAndMattermostVersion() (string, string) {
mattermostVersion, _ := s.Store.System().GetByName("Version")
return *s.Config().SqlSettings.DriverName, mattermostVersion.Value
// Return Database type (postgres or mysql) and current version of the schema
func (s *Server) DatabaseTypeAndSchemaVersion() (string, string) {
schemaVersion, _ := s.Store.GetDBSchemaVersion()
return *s.Config().SqlSettings.DriverName, strconv.Itoa(schemaVersion)
}
// initLogging initializes and configures the logger(s). This may be called more than once.
@@ -2194,13 +2195,15 @@ func (a *App) generateSupportPacketYaml() (*model.FileData, string) {
vendorName, vendorVersion = ldapInterface.GetVendorNameAndVendorVersion()
}
// Here we are getting information regarding the database (mysql/postgres + current Mattermost version)
databaseType, databaseVersion := a.Srv().DatabaseTypeAndMattermostVersion()
// Here we are getting information regarding the database (mysql/postgres + current schema version)
databaseType, databaseVersion := a.Srv().DatabaseTypeAndSchemaVersion()
// Creating the struct for support packet yaml file
supportPacket := model.SupportPacket{
ServerOS: runtime.GOOS,
ServerArchitecture: runtime.GOARCH,
ServerVersion: model.CurrentVersion,
BuildHash: model.BuildHash,
DatabaseType: databaseType,
DatabaseVersion: databaseVersion,
LdapVendorName: vendorName,
@@ -2304,3 +2307,11 @@ func runDNDStatusExpireJob(a *App) {
}
})
}
func (a *App) GetAppliedSchemaMigrations() ([]model.AppliedMigration, *model.AppError) {
table, err := a.Srv().Store.GetAppliedMigrations()
if err != nil {
return nil, model.NewAppError("GetDBSchemaTable", "api.file.read_file.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return table, nil
}

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

@@ -233,18 +233,18 @@ func TestDatabaseTypeAndMattermostVersion(t *testing.T) {
th := Setup(t)
defer th.TearDown()
databaseType, mattermostVersion := th.Server.DatabaseTypeAndMattermostVersion()
databaseType, mattermostVersion := th.Server.DatabaseTypeAndSchemaVersion()
assert.Equal(t, "postgres", databaseType)
assert.Equal(t, "5.31.0", mattermostVersion)
assert.GreaterOrEqual(t, mattermostVersion, strconv.Itoa(1))
os.Setenv("MM_SQLSETTINGS_DRIVERNAME", "mysql")
th2 := Setup(t)
defer th2.TearDown()
databaseType, mattermostVersion = th2.Server.DatabaseTypeAndMattermostVersion()
databaseType, mattermostVersion = th2.Server.DatabaseTypeAndSchemaVersion()
assert.Equal(t, "mysql", databaseType)
assert.Equal(t, "5.31.0", mattermostVersion)
assert.GreaterOrEqual(t, mattermostVersion, strconv.Itoa(1))
}
func TestGenerateSupportPacket(t *testing.T) {

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

@@ -882,6 +882,7 @@ func TestLeaveTeamPanic(t *testing.T) {
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("License").Return(&mockLicenseStore)
mockStore.On("Team").Return(&mockTeamStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
team := &model.Team{Id: "myteam"}
user := &model.User{Id: "userID"}
@@ -1239,6 +1240,7 @@ func TestClearTeamMembersCache(t *testing.T) {
TeamId: "1",
}}, nil)
mockStore.On("Team").Return(&mockTeamStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.ClearTeamMembersCache("teamID")
}

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

@@ -161,6 +161,7 @@ func TestHubSessionRevokeRace(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
userService, err := users.New(users.ServiceConfig{
UserStore: &mockUserStore,

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

@@ -5,6 +5,7 @@ package commands
import (
"fmt"
"strconv"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -51,13 +52,21 @@ var MigrateCmd = &cobra.Command{
RunE: migrateCmdF,
}
var DBVersionCmd = &cobra.Command{
Use: "version",
Short: "Returns the recent applied version number",
RunE: dbVersionCmdF,
}
func init() {
ResetCmd.Flags().Bool("confirm", false, "Confirm you really want to delete everything and a DB backup has been performed.")
DBVersionCmd.Flags().Bool("all", false, "Returns all applied migrations")
DbCmd.AddCommand(
InitDbCmd,
ResetCmd,
MigrateCmd,
DBVersionCmd,
)
RootCmd.AddCommand(
@@ -137,3 +146,35 @@ func migrateCmdF(command *cobra.Command, args []string) error {
return nil
}
func dbVersionCmdF(command *cobra.Command, args []string) error {
cfgDSN := getConfigDSN(command, config.GetEnvironment())
cfgStore, err := config.NewStoreFromDSN(cfgDSN, true, nil, true)
if err != nil {
return errors.Wrap(err, "failed to load configuration")
}
config := cfgStore.Get()
store := sqlstore.New(config.SqlSettings, nil)
defer store.Close()
allFlag, _ := command.Flags().GetBool("all")
if allFlag {
applied, err2 := store.GetAppliedMigrations()
if err2 != nil {
return errors.Wrap(err2, "failed to get applied migrations")
}
for _, migration := range applied {
CommandPrettyPrintln(fmt.Sprintf("Varsion: %d, Name: %s", migration.Version, migration.Name))
}
return nil
}
v, err := store.GetDBSchemaVersion()
if err != nil {
return errors.Wrap(err, "failed to get schema version")
}
CommandPrettyPrintln("Current database schema version is: " + strconv.Itoa(v))
return nil
}

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

@@ -6155,6 +6155,10 @@
"id": "app.submit_interactive_dialog.json_error",
"translation": "Encountered an error encoding JSON for the interactive dialog."
},
{
"id": "app.system.applied_migrations.not_authorized",
"translation": "You don't have the appropriate permissions."
},
{
"id": "app.system.complete_onboarding_request.app_error",
"translation": "Failed to decode the complete onboarding request."

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

@@ -7931,3 +7931,16 @@ func (c *Client4) GetUsersWithInvalidEmails(page, perPage int) ([]*User, *Respon
}
return list, BuildResponse(r), nil
}
func (c *Client4) GetAppliedSchemaMigrations() ([]AppliedMigration, *Response, error) {
r, err := c.DoAPIGet(c.systemRoute()+"/schema/version", "")
if err != nil {
return nil, BuildResponse(r), err
}
defer closeBody(r)
var list []AppliedMigration
if jsonErr := json.NewDecoder(r.Body).Decode(&list); jsonErr != nil {
return nil, nil, NewAppError("GetUsers", "api.unmarshal_error", nil, jsonErr.Error(), http.StatusInternalServerError)
}
return list, BuildResponse(r), nil
}

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

@@ -79,6 +79,8 @@ type ServerBusyState struct {
type SupportPacket struct {
ServerOS string `yaml:"server_os"`
ServerArchitecture string `yaml:"server_architecture"`
ServerVersion string `yaml:"server_version"`
BuildHash string `yaml:"build_hash,omitempty"`
DatabaseType string `yaml:"database_type"`
DatabaseVersion string `yaml:"database_version"`
LdapVendorName string `yaml:"ldap_vendor_name,omitempty"`
@@ -173,3 +175,8 @@ type WarnMetricStatus struct {
type SendWarnMetricAck struct {
ForceAck bool `json:"forceAck"`
}
type AppliedMigration struct {
Version int `json:"version"`
Name string `json:"name"`
}

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

@@ -11835,10 +11835,6 @@ func (s *OpenTracingLayer) DropAllTables() {
s.Store.DropAllTables()
}
func (s *OpenTracingLayer) GetCurrentSchemaVersion() string {
return s.Store.GetCurrentSchemaVersion()
}
func (s *OpenTracingLayer) LockToMaster() {
s.Store.LockToMaster()
}

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

@@ -13485,10 +13485,6 @@ func (s *RetryLayer) DropAllTables() {
s.Store.DropAllTables()
}
func (s *RetryLayer) GetCurrentSchemaVersion() string {
return s.Store.GetCurrentSchemaVersion()
}
func (s *RetryLayer) LockToMaster() {
s.Store.LockToMaster()
}

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

@@ -353,10 +353,10 @@ func (ss *SqlStore) DriverName() string {
return *ss.settings.DriverName
}
func (ss *SqlStore) GetCurrentSchemaVersion() string {
func (ss *SqlStore) getCurrentSchemaVersion() (string, error) {
var version string
_ = ss.GetMasterX().Get(&version, "SELECT Value FROM Systems WHERE Name='Version'")
return version
err := ss.GetMasterX().Get(&version, "SELECT Value FROM Systems WHERE Name='Version'")
return version, err
}
// GetDbVersion returns the version of the database being used.
@@ -949,6 +949,7 @@ func (ss *SqlStore) DropAllTables() {
FROM pg_class
WHERE relkind = 'r' -- only tables
AND relnamespace = 'public'::regnamespace
AND NOT relname = 'db_migrations'
);
END
$func$;`)
@@ -956,7 +957,9 @@ func (ss *SqlStore) DropAllTables() {
tables := []string{}
ss.masterX.Select(&tables, `show tables`)
for _, t := range tables {
ss.masterX.Exec(`TRUNCATE TABLE ` + t)
if t != "db_migrations" {
ss.masterX.Exec(`TRUNCATE TABLE ` + t)
}
}
}
}
@@ -1204,3 +1207,20 @@ func (ss *SqlStore) toReserveCase(str string) string {
return fmt.Sprintf("`%s`", strings.Title(str))
}
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 {
return 0, errors.Wrap(err, "unable to select from db_migrations")
}
return version, nil
}
func (ss *SqlStore) GetAppliedMigrations() ([]model.AppliedMigration, error) {
migrations := []model.AppliedMigration{}
if err := ss.GetMasterX().Select(&migrations, "SELECT Version, Name FROM db_migrations ORDER BY Version DESC"); err != nil {
return nil, errors.Wrap(err, "unable to select from db_migrations")
}
return migrations, nil
}

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

@@ -6,7 +6,11 @@ package sqlstore
import (
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"testing"
"time"
@@ -18,6 +22,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/v6/db"
"github.com/mattermost/mattermost-server/v6/einterfaces/mocks"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/store"
@@ -784,3 +789,79 @@ func TestMySQLReadTimeout(t *testing.T) {
_, err = store.GetMasterX().ExecNoTimeout(`SELECT SLEEP(3)`)
require.NoError(t, err)
}
func TestGetDBSchemaVersion(t *testing.T) {
testDrivers := []string{
model.DatabaseDriverPostgres,
model.DatabaseDriverMysql,
}
assets := db.Assets()
for _, driver := range testDrivers {
t.Run("Should return latest version number of applied migrations for "+driver, func(t *testing.T) {
t.Parallel()
settings := makeSqlSettings(driver)
store := New(*settings, nil)
assetsList, err := assets.ReadDir(filepath.Join("migrations", driver))
require.NoError(t, err)
var assetNamesForDriver []string
for _, entry := range assetsList {
assetNamesForDriver = append(assetNamesForDriver, entry.Name())
}
sort.Strings(assetNamesForDriver)
require.NotEmpty(t, assetNamesForDriver)
lastMigration := assetNamesForDriver[len(assetNamesForDriver)-1]
expectedVersion := strings.Split(lastMigration, "_")[0]
version, err := store.GetDBSchemaVersion()
require.NoError(t, err)
require.Equal(t, expectedVersion, fmt.Sprintf("%06d", version))
})
}
}
func TestGetAppliedMigrations(t *testing.T) {
testDrivers := []string{
model.DatabaseDriverPostgres,
model.DatabaseDriverMysql,
}
assets := db.Assets()
for _, driver := range testDrivers {
t.Run("Should return db applied migrations for "+driver, func(t *testing.T) {
t.Parallel()
settings := makeSqlSettings(driver)
store := New(*settings, nil)
assetsList, err := assets.ReadDir(filepath.Join("migrations", driver))
require.NoError(t, err)
var migrationsFromFiles []model.AppliedMigration
for _, entry := range assetsList {
if strings.HasSuffix(entry.Name(), ".up.sql") {
versionString := strings.Split(entry.Name(), "_")[0]
version, vErr := strconv.Atoi(versionString)
require.NoError(t, vErr)
name := strings.TrimSuffix(strings.TrimLeft(entry.Name(), versionString+"_"), ".up.sql")
migrationsFromFiles = append(migrationsFromFiles, model.AppliedMigration{
Version: version,
Name: name,
})
}
}
require.NotEmpty(t, migrationsFromFiles)
migrations, err := store.GetAppliedMigrations()
require.NoError(t, err)
require.ElementsMatch(t, migrationsFromFiles, migrations)
})
}
}

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

@@ -114,8 +114,12 @@ func upgradeDatabase(sqlStore *SqlStore, currentModelVersionString string) error
return errors.Wrapf(err, "failed to parse oldest supported version %s", OldestSupportedVersion)
}
currentSchemaVersionString, err := sqlStore.getCurrentSchemaVersion()
if err != nil {
mlog.Warn("could not receive the schema version from systems table", mlog.Err(err))
}
var currentSchemaVersion *semver.Version
currentSchemaVersionString := sqlStore.GetCurrentSchemaVersion()
if currentSchemaVersionString != "" {
currentSchemaVersion, err = semver.New(currentSchemaVersionString)
if err != nil {
@@ -232,7 +236,11 @@ func saveSchemaVersion(sqlStore *SqlStore, version string) {
}
func shouldPerformUpgrade(sqlStore *SqlStore, currentSchemaVersion string, expectedSchemaVersion string) bool {
storedSchemaVersion := sqlStore.GetCurrentSchemaVersion()
storedSchemaVersion, err := sqlStore.getCurrentSchemaVersion()
if err != nil {
mlog.Error("could not receive the schema version from systems table", mlog.Err(err))
return false
}
storedVersion, err := semver.Parse(storedSchemaVersion)
if err != nil {

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

@@ -17,7 +17,11 @@ func TestStoreUpgradeDotRelease(t *testing.T) {
saveSchemaVersion(sqlStore, "5.33.1")
err := upgradeDatabase(sqlStore, CurrentSchemaVersion)
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, currentVersion)
})
}
@@ -34,28 +38,44 @@ func TestStoreUpgrade(t *testing.T) {
saveSchemaVersion(sqlStore, "invalid")
err := upgradeDatabase(sqlStore, "5.8.0")
require.EqualError(t, err, "failed to parse database schema version invalid: No Major.Minor.Patch elements found")
require.Equal(t, "invalid", sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, "invalid", currentVersion)
})
t.Run("upgrade from unsupported version", func(t *testing.T) {
saveSchemaVersion(sqlStore, "2.0.0")
err := upgradeDatabase(sqlStore, "5.8.0")
require.EqualError(t, err, "Database schema version 2.0.0 is no longer supported. This Mattermost server supports automatic upgrades from schema version 3.0.0 through schema version 5.8.0. Please manually upgrade to at least version 3.0.0 before continuing.")
require.Equal(t, "2.0.0", sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, "2.0.0", currentVersion)
})
t.Run("upgrade from earliest supported version", func(t *testing.T) {
saveSchemaVersion(sqlStore, Version300)
err := upgradeDatabase(sqlStore, CurrentSchemaVersion)
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, currentVersion)
})
t.Run("upgrade from no existing version", func(t *testing.T) {
saveSchemaVersion(sqlStore, "")
err := upgradeDatabase(sqlStore, CurrentSchemaVersion)
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, currentVersion)
})
t.Run("upgrade schema running earlier minor version", func(t *testing.T) {
@@ -64,28 +84,44 @@ func TestStoreUpgrade(t *testing.T) {
require.NoError(t, err)
// Assert CurrentSchemaVersion, not 5.8.0, since the migrations will move
// past 5.8.0 regardless of the input parameter.
require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, currentVersion)
})
t.Run("upgrade schema running later minor version", func(t *testing.T) {
saveSchemaVersion(sqlStore, "5.99.0")
err := upgradeDatabase(sqlStore, "5.8.0")
require.NoError(t, err)
require.Equal(t, "5.99.0", sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, "5.99.0", currentVersion)
})
t.Run("upgrade schema running earlier major version", func(t *testing.T) {
saveSchemaVersion(sqlStore, "4.1.0")
err := upgradeDatabase(sqlStore, CurrentSchemaVersion)
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, currentVersion)
})
t.Run("upgrade schema running later major version", func(t *testing.T) {
saveSchemaVersion(sqlStore, "6.0.0")
err := upgradeDatabase(sqlStore, "5.8.0")
require.EqualError(t, err, "Database schema version 6.0.0 is not supported. This Mattermost server supports only >=5.8.0, <6.0.0. Please upgrade to at least version 6.0.0 before continuing.")
require.Equal(t, "6.0.0", sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, "6.0.0", currentVersion)
})
})
}
@@ -100,7 +136,11 @@ func TestSaveSchemaVersion(t *testing.T) {
require.NoError(t, err)
require.Equal(t, Version300, props["Version"])
require.Equal(t, Version300, sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, Version300, currentVersion)
})
t.Run("set current version", func(t *testing.T) {
@@ -109,7 +149,11 @@ func TestSaveSchemaVersion(t *testing.T) {
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, props["Version"])
require.Equal(t, CurrentSchemaVersion, sqlStore.GetCurrentSchemaVersion())
currentVersion, err := sqlStore.getCurrentSchemaVersion()
require.NoError(t, err)
require.Equal(t, CurrentSchemaVersion, currentVersion)
})
})
}

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

@@ -63,7 +63,8 @@ type Store interface {
UnlockFromMaster()
DropAllTables()
RecycleDBConnections(d time.Duration)
GetCurrentSchemaVersion() string
GetDBSchemaVersion() (int, error)
GetAppliedMigrations() ([]model.AppliedMigration, error)
GetDbVersion(numerical bool) (string, error)
TotalMasterDbConnections() int
TotalReadDbConnections() int

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

@@ -222,18 +222,48 @@ func (_m *Store) FileInfo() store.FileInfoStore {
return r0
}
// GetCurrentSchemaVersion provides a mock function with given fields:
func (_m *Store) GetCurrentSchemaVersion() string {
// GetAppliedMigrations provides a mock function with given fields:
func (_m *Store) GetAppliedMigrations() ([]model.AppliedMigration, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
var r0 []model.AppliedMigration
if rf, ok := ret.Get(0).(func() []model.AppliedMigration); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
if ret.Get(0) != nil {
r0 = ret.Get(0).([]model.AppliedMigration)
}
}
return r0
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetDBSchemaVersion provides a mock function with given fields:
func (_m *Store) GetDBSchemaVersion() (int, error) {
ret := _m.Called()
var r0 int
if rf, ok := ret.Get(0).(func() int); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetDbVersion provides a mock function with given fields: numerical

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

@@ -104,10 +104,13 @@ func (s *Store) UnlockFromMaster() { /* do nothing */ }
func (s *Store) DropAllTables() { /* do nothing */ }
func (s *Store) GetDbVersion(bool) (string, error) { return "", nil }
func (s *Store) RecycleDBConnections(time.Duration) {}
func (s *Store) TotalMasterDbConnections() int { return 1 }
func (s *Store) TotalReadDbConnections() int { return 1 }
func (s *Store) TotalSearchDbConnections() int { return 1 }
func (s *Store) GetCurrentSchemaVersion() string { return "" }
func (s *Store) GetDBSchemaVersion() (int, error) { return 1, nil }
func (s *Store) GetAppliedMigrations() ([]model.AppliedMigration, error) {
return []model.AppliedMigration{}, nil
}
func (s *Store) TotalMasterDbConnections() int { return 1 }
func (s *Store) TotalReadDbConnections() int { return 1 }
func (s *Store) TotalSearchDbConnections() int { return 1 }
func (s *Store) CheckIntegrity() <-chan model.IntegrityCheckResult {
return make(chan model.IntegrityCheckResult)
}

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

@@ -10664,10 +10664,6 @@ func (s *TimerLayer) DropAllTables() {
s.Store.DropAllTables()
}
func (s *TimerLayer) GetCurrentSchemaVersion() string {
return s.Store.GetCurrentSchemaVersion()
}
func (s *TimerLayer) LockToMaster() {
s.Store.LockToMaster()
}

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

@@ -110,5 +110,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
mockStore.On("Session").Return(&sessionStore)
mockStore.On("OAuth").Return(&oAuthStore)
mockStore.On("Group").Return(&groupStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
return &mockStore
}

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

@@ -68,6 +68,7 @@ func TestMfaRequired(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.Srv().SetLicense(model.NewTestLicense("mfa"))

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

@@ -79,6 +79,7 @@ func TestHandlerServeHTTPSecureTransport(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.UpdateConfig(func(config *model.Config) {
*config.ServiceSettings.TLSStrictTransport = true
@@ -321,6 +322,7 @@ func TestHandlerServeCSPHeader(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.SiteURL = *cfg.ServiceSettings.SiteURL + "/subpath"
@@ -641,6 +643,7 @@ func TestCheckCSRFToken(t *testing.T) {
mockStore.On("User").Return(&mockUserStore)
mockStore.On("Post").Return(&mockPostStore)
mockStore.On("System").Return(&mockSystemStore)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.ExperimentalStrictCSRFEnforcement = true