mirror of https://github.com/portainer/portainer
fix(backups): fix rollback feature [EE-6367] (#10691)
parent
76bcdfa2b8
commit
db46dc553f
|
@ -49,7 +49,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) {
|
|||
SSL: kingpin.Flag("ssl", "Secure Portainer instance using SSL (deprecated)").Default(defaultSSL).Bool(),
|
||||
SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").String(),
|
||||
SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").String(),
|
||||
Rollback: kingpin.Flag("rollback", "Rollback the database store to the previous version").Bool(),
|
||||
Rollback: kingpin.Flag("rollback", "Rollback the database to the previous backup").Bool(),
|
||||
SnapshotInterval: kingpin.Flag("snapshot-interval", "Duration between each environment snapshot job").String(),
|
||||
AdminPassword: kingpin.Flag("admin-password", "Set admin password with provided hash").String(),
|
||||
AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(),
|
||||
|
|
|
@ -65,7 +65,7 @@ type BackupOptions struct {
|
|||
// - db rollback
|
||||
func getBackupRestoreOptions(backupDir string) *BackupOptions {
|
||||
return &BackupOptions{
|
||||
BackupDir: backupDir, //connection.commonBackupDir(),
|
||||
BackupDir: backupDir,
|
||||
BackupFileName: beforePortainerVersionUpgradeBackup,
|
||||
}
|
||||
}
|
||||
|
@ -76,12 +76,12 @@ func (store *Store) Backup(version *models.Version) (string, error) {
|
|||
return store.backupWithOptions(nil)
|
||||
}
|
||||
|
||||
return store.backupWithOptions(&BackupOptions{
|
||||
Version: version.SchemaVersion,
|
||||
})
|
||||
backupOptions := getBackupRestoreOptions(store.commonBackupDir())
|
||||
backupOptions.Version = version.SchemaVersion
|
||||
return store.backupWithOptions(backupOptions)
|
||||
}
|
||||
|
||||
func (store *Store) setupOptions(options *BackupOptions) *BackupOptions {
|
||||
func (store *Store) setDefaultBackupOptions(options *BackupOptions) *BackupOptions {
|
||||
if options == nil {
|
||||
options = &BackupOptions{}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
|||
|
||||
store.createBackupFolders()
|
||||
|
||||
options = store.setupOptions(options)
|
||||
options = store.setDefaultBackupOptions(options)
|
||||
dbPath := store.databasePath()
|
||||
|
||||
if err := store.Close(); err != nil {
|
||||
|
@ -139,20 +139,18 @@ func (store *Store) backupWithOptions(options *BackupOptions) (string, error) {
|
|||
// - default: restore latest from current edition
|
||||
// - restore a specific
|
||||
func (store *Store) restoreWithOptions(options *BackupOptions) error {
|
||||
options = store.setupOptions(options)
|
||||
options = store.setDefaultBackupOptions(options)
|
||||
|
||||
// Check if backup file exist before restoring
|
||||
_, err := os.Stat(options.BackupPath)
|
||||
if os.IsNotExist(err) {
|
||||
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to restore does not exist %s")
|
||||
|
||||
log.Error().Str("path", options.BackupPath).Err(err).Msg("backup file to restore does not exist")
|
||||
return err
|
||||
}
|
||||
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while closing store before restore")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -170,7 +168,7 @@ func (store *Store) restoreWithOptions(options *BackupOptions) error {
|
|||
func (store *Store) removeWithOptions(options *BackupOptions) error {
|
||||
log.Info().Msg("removing DB backup")
|
||||
|
||||
options = store.setupOptions(options)
|
||||
options = store.setDefaultBackupOptions(options)
|
||||
_, err := os.Stat(options.BackupPath)
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package datastore
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
@ -117,6 +118,11 @@ func (store *Store) FailSafeMigrate(migrator *migrator.Migrator, version *models
|
|||
return err
|
||||
}
|
||||
|
||||
// Special test code to simulate a failure (used by migrate_data_test.go). Do not remove...
|
||||
if os.Getenv("PORTAINER_TEST_MIGRATE_FAIL") == "FAIL" {
|
||||
panic("test migration failure")
|
||||
}
|
||||
|
||||
err = store.VersionService.StoreIsUpdating(false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to update the store")
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/database/boltdb"
|
||||
"github.com/portainer/portainer/api/database/models"
|
||||
|
||||
|
@ -55,111 +56,108 @@ func TestMigrateData(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
|
||||
// newStore, store, teardown := MustNewTestStore(t, true, false)
|
||||
// defer teardown()
|
||||
t.Run("MigrateData for New Store & Re-Open Check", func(t *testing.T) {
|
||||
newStore, store := MustNewTestStore(t, true, false)
|
||||
|
||||
// if !newStore {
|
||||
// t.Error("Expect a new DB")
|
||||
// }
|
||||
if !newStore {
|
||||
t.Error("Expect a new DB")
|
||||
}
|
||||
|
||||
// testVersion(store, portainer.APIVersion, t)
|
||||
// store.Close()
|
||||
testVersion(store, portainer.APIVersion, t)
|
||||
store.Close()
|
||||
|
||||
// newStore, _ = store.Open()
|
||||
// if newStore {
|
||||
// t.Error("Expect store to NOT be new DB")
|
||||
// }
|
||||
// })
|
||||
newStore, _ = store.Open()
|
||||
if newStore {
|
||||
t.Error("Expect store to NOT be new DB")
|
||||
}
|
||||
})
|
||||
|
||||
// tests := []struct {
|
||||
// version string
|
||||
// expectedVersion string
|
||||
// }{
|
||||
// {version: "1.24.1", expectedVersion: portainer.APIVersion},
|
||||
// {version: "2.0.0", expectedVersion: portainer.APIVersion},
|
||||
// }
|
||||
// for _, tc := range tests {
|
||||
// _, store, teardown := MustNewTestStore(t, true, true)
|
||||
// defer teardown()
|
||||
tests := []struct {
|
||||
version string
|
||||
expectedVersion string
|
||||
}{
|
||||
{version: "1.24.1", expectedVersion: portainer.APIVersion},
|
||||
{version: "2.0.0", expectedVersion: portainer.APIVersion},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
_, store := MustNewTestStore(t, true, true)
|
||||
|
||||
// // Setup data
|
||||
// v := models.Version{SchemaVersion: tc.version}
|
||||
// store.VersionService.UpdateVersion(&v)
|
||||
// Setup data
|
||||
v := models.Version{SchemaVersion: tc.version, Edition: int(portainer.PortainerCE)}
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
|
||||
// // Required roles by migrations 22.2
|
||||
// store.RoleService.Create(&portainer.Role{ID: 1})
|
||||
// store.RoleService.Create(&portainer.Role{ID: 2})
|
||||
// store.RoleService.Create(&portainer.Role{ID: 3})
|
||||
// store.RoleService.Create(&portainer.Role{ID: 4})
|
||||
// Required roles by migrations 22.2
|
||||
store.RoleService.Create(&portainer.Role{ID: 1})
|
||||
store.RoleService.Create(&portainer.Role{ID: 2})
|
||||
store.RoleService.Create(&portainer.Role{ID: 3})
|
||||
store.RoleService.Create(&portainer.Role{ID: 4})
|
||||
|
||||
// t.Run(fmt.Sprintf("MigrateData for version %s", tc.version), func(t *testing.T) {
|
||||
// store.MigrateData()
|
||||
// testVersion(store, tc.expectedVersion, t)
|
||||
// })
|
||||
t.Run(fmt.Sprintf("MigrateData for version %s", tc.version), func(t *testing.T) {
|
||||
store.MigrateData()
|
||||
testVersion(store, tc.expectedVersion, t)
|
||||
})
|
||||
|
||||
// t.Run(fmt.Sprintf("Restoring DB after migrateData for version %s", tc.version), func(t *testing.T) {
|
||||
// store.Rollback(true)
|
||||
// store.Open()
|
||||
// testVersion(store, tc.version, t)
|
||||
// })
|
||||
// }
|
||||
t.Run(fmt.Sprintf("Restoring DB after migrateData for version %s", tc.version), func(t *testing.T) {
|
||||
store.Rollback(true)
|
||||
store.Open()
|
||||
testVersion(store, tc.version, t)
|
||||
})
|
||||
}
|
||||
|
||||
// t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
|
||||
// _, store, teardown := MustNewTestStore(t, false, true)
|
||||
// defer teardown()
|
||||
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, false, false)
|
||||
|
||||
// v := models.Version{SchemaVersion: "1.24.1"}
|
||||
// store.VersionService.UpdateVersion(&v)
|
||||
v := models.Version{SchemaVersion: "1.24.1", Edition: int(portainer.PortainerCE)}
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
|
||||
// store.MigrateData()
|
||||
store.MigrateData()
|
||||
|
||||
// testVersion(store, v.SchemaVersion, t)
|
||||
// })
|
||||
testVersion(store, v.SchemaVersion, t)
|
||||
})
|
||||
|
||||
// t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
|
||||
// _, store, teardown := MustNewTestStore(t, false, true)
|
||||
// defer teardown()
|
||||
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, false, false)
|
||||
|
||||
// v := models.Version{SchemaVersion: "0.0.0"}
|
||||
// store.VersionService.UpdateVersion(&v)
|
||||
v := models.Version{SchemaVersion: "0.0.0", Edition: int(portainer.PortainerCE)}
|
||||
store.VersionService.UpdateVersion(&v)
|
||||
|
||||
// store.MigrateData()
|
||||
store.MigrateData()
|
||||
|
||||
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
options := store.setDefaultBackupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
// if !isFileExist(options.BackupPath) {
|
||||
// t.Errorf("Backup file should exist; file=%s", options.BackupPath)
|
||||
// }
|
||||
// })
|
||||
if !isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should exist; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
// t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
|
||||
// _, store, teardown := MustNewTestStore(t, false, true)
|
||||
// defer teardown()
|
||||
t.Run("MigrateData should fail to create backup if database file is set to updating", func(t *testing.T) {
|
||||
_, store := MustNewTestStore(t, false, false)
|
||||
|
||||
// store.VersionService.StoreIsUpdating(true)
|
||||
store.VersionService.StoreIsUpdating(true)
|
||||
|
||||
// store.MigrateData()
|
||||
store.MigrateData()
|
||||
|
||||
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
options := store.setDefaultBackupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
// if isFileExist(options.BackupPath) {
|
||||
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
// }
|
||||
// })
|
||||
if isFileExist(options.BackupPath) {
|
||||
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
}
|
||||
})
|
||||
|
||||
// t.Run("MigrateData should not create backup on startup if portainer version matches db", func(t *testing.T) {
|
||||
// _, store, teardown := MustNewTestStore(t, false, true)
|
||||
// defer teardown()
|
||||
t.Run("MigrateData should recover and restore backup during migration critical failure", func(t *testing.T) {
|
||||
os.Setenv("PORTAINER_TEST_MIGRATE_FAIL", "FAIL")
|
||||
|
||||
// store.MigrateData()
|
||||
version := "2.15"
|
||||
_, store := MustNewTestStore(t, true, false)
|
||||
store.VersionService.UpdateVersion(&models.Version{SchemaVersion: version, Edition: int(portainer.PortainerCE)})
|
||||
err := store.MigrateData()
|
||||
if err == nil {
|
||||
t.Errorf("Expect migration to fail")
|
||||
}
|
||||
|
||||
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
|
||||
|
||||
// if isFileExist(options.BackupPath) {
|
||||
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
|
||||
// }
|
||||
// })
|
||||
store.Open()
|
||||
testVersion(store, version, t)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getBackupRestoreOptions(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue