fix(backups): fix rollback feature [EE-6367] (#10691)

pull/10696/head
Matt Hook 1 year ago committed by GitHub
parent 76bcdfa2b8
commit db46dc553f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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")
// }
// testVersion(store, portainer.APIVersion, t)
// store.Close()
// 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()
// // Setup data
// v := models.Version{SchemaVersion: tc.version}
// store.VersionService.UpdateVersion(&v)
if !newStore {
t.Error("Expect a new DB")
}
// // 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})
testVersion(store, portainer.APIVersion, t)
store.Close()
// t.Run(fmt.Sprintf("MigrateData for version %s", tc.version), func(t *testing.T) {
// store.MigrateData()
// testVersion(store, tc.expectedVersion, t)
// })
newStore, _ = store.Open()
if newStore {
t.Error("Expect store to NOT be new DB")
}
})
// 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)
// })
// }
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, 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})
t.Run(fmt.Sprintf("MigrateData for version %s", tc.version), func(t *testing.T) {
store.MigrateData()
testVersion(store, tc.expectedVersion, 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(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)
})
}
// v := models.Version{SchemaVersion: "1.24.1"}
// store.VersionService.UpdateVersion(&v)
t.Run("Error in MigrateData should restore backup before MigrateData", func(t *testing.T) {
_, store := MustNewTestStore(t, false, false)
// store.MigrateData()
v := models.Version{SchemaVersion: "1.24.1", Edition: int(portainer.PortainerCE)}
store.VersionService.UpdateVersion(&v)
// testVersion(store, v.SchemaVersion, t)
// })
store.MigrateData()
// t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
// _, store, teardown := MustNewTestStore(t, false, true)
// defer teardown()
testVersion(store, v.SchemaVersion, t)
})
// v := models.Version{SchemaVersion: "0.0.0"}
// store.VersionService.UpdateVersion(&v)
t.Run("MigrateData should create backup file upon update", func(t *testing.T) {
_, store := MustNewTestStore(t, false, false)
// store.MigrateData()
v := models.Version{SchemaVersion: "0.0.0", Edition: int(portainer.PortainerCE)}
store.VersionService.UpdateVersion(&v)
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
store.MigrateData()
// if !isFileExist(options.BackupPath) {
// t.Errorf("Backup file should exist; file=%s", options.BackupPath)
// }
// })
options := store.setDefaultBackupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// 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()
if !isFileExist(options.BackupPath) {
t.Errorf("Backup file should exist; file=%s", options.BackupPath)
}
})
// store.VersionService.StoreIsUpdating(true)
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.MigrateData()
store.VersionService.StoreIsUpdating(true)
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
store.MigrateData()
// if isFileExist(options.BackupPath) {
// t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
// }
// })
options := store.setDefaultBackupOptions(getBackupRestoreOptions(store.commonBackupDir()))
// 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()
if isFileExist(options.BackupPath) {
t.Errorf("Backup file should not exist for dirty database; file=%s", options.BackupPath)
}
})
// store.MigrateData()
t.Run("MigrateData should recover and restore backup during migration critical failure", func(t *testing.T) {
os.Setenv("PORTAINER_TEST_MIGRATE_FAIL", "FAIL")
// options := store.setupOptions(getBackupRestoreOptions(store.commonBackupDir()))
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")
}
// 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…
Cancel
Save