Harden self-update recovery (T-617)
This commit is contained in:
@@ -15,7 +15,7 @@ func recoverLayout(layout updateLayout, syncer DirectorySyncer) error {
|
||||
layout.backup = filepath.Join(layout.root, backupsDirectoryName, record.RequestID)
|
||||
switch record.Phase {
|
||||
case phasePrepared:
|
||||
return removeTransaction(layout, syncer)
|
||||
return recoverPrepared(layout, syncer)
|
||||
case phaseTargetBackedUp:
|
||||
if err := restoreBackup(layout, syncer); err != nil {
|
||||
return err
|
||||
@@ -39,6 +39,33 @@ func recoverLayout(layout updateLayout, syncer DirectorySyncer) error {
|
||||
}
|
||||
}
|
||||
|
||||
// recoverPrepared reconciles the only phase where the durable journal may be
|
||||
// present while the first rename was interrupted between Rename and its
|
||||
// directory fence. The physical managed layout is authoritative here: blindly
|
||||
// removing the journal would strand the old app in backup after such a fence
|
||||
// failure.
|
||||
func recoverPrepared(layout updateLayout, syncer DirectorySyncer) error {
|
||||
targetExists, err := realDirectoryState(layout.target, "target")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backupExists, err := realDirectoryState(layout.backup, "backup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case targetExists && !backupExists:
|
||||
return removeTransaction(layout, syncer)
|
||||
case !targetExists && backupExists:
|
||||
if err := restoreBackup(layout, syncer); err != nil {
|
||||
return err
|
||||
}
|
||||
return removeTransaction(layout, syncer)
|
||||
default:
|
||||
return fmt.Errorf("%w: prepared transaction has target=%t backup=%t", ErrRecoveryRequired, targetExists, backupExists)
|
||||
}
|
||||
}
|
||||
|
||||
func rollbackLayout(layout updateLayout, syncer DirectorySyncer) error {
|
||||
backupExists, err := realDirectoryState(layout.backup, "backup")
|
||||
if err != nil || !backupExists {
|
||||
@@ -57,11 +84,11 @@ func rollbackLayout(layout updateLayout, syncer DirectorySyncer) error {
|
||||
return fmt.Errorf("%w: staged and active app both exist", ErrRecoveryRequired)
|
||||
}
|
||||
if err := renameDirectory(layout.target, layout.staging, layout, syncer, "preserve failed staged app"); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrRecoveryRequired, err)
|
||||
return fmt.Errorf("%w: %w", ErrRecoveryRequired, err)
|
||||
}
|
||||
}
|
||||
if err := renameDirectory(layout.backup, layout.target, layout, syncer, "restore previous app"); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrRecoveryRequired, err)
|
||||
return fmt.Errorf("%w: %w", ErrRecoveryRequired, err)
|
||||
}
|
||||
if err := removeTransaction(layout, syncer); err != nil {
|
||||
return err
|
||||
@@ -87,7 +114,7 @@ func restoreBackup(layout updateLayout, syncer DirectorySyncer) error {
|
||||
return fmt.Errorf("%w: backup is unavailable", ErrRecoveryRequired)
|
||||
}
|
||||
if err := renameDirectory(layout.backup, layout.target, layout, syncer, "restore previous app"); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrRecoveryRequired, err)
|
||||
return fmt.Errorf("%w: %w", ErrRecoveryRequired, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user