Fence installation transaction durability (T-613)
This commit is contained in:
+61
-23
@@ -42,18 +42,20 @@ func (err *RollbackError) Unwrap() error {
|
||||
|
||||
// Switcher activates a verified staging directory and runs an injected check.
|
||||
type Switcher struct {
|
||||
health HealthCheck
|
||||
afterStep func(switchStep) error
|
||||
health HealthCheck
|
||||
afterStep func(switchStep) error
|
||||
durability durabilityFence
|
||||
}
|
||||
|
||||
func NewSwitcher(health HealthCheck) *Switcher {
|
||||
return &Switcher{health: health}
|
||||
return &Switcher{health: health, durability: defaultDurability()}
|
||||
}
|
||||
|
||||
func (switcher *Switcher) Switch(root string) error {
|
||||
if switcher.health == nil {
|
||||
return ErrHealthCheckRequired
|
||||
}
|
||||
fence := effectiveDurability(switcher.durability)
|
||||
layout, err := inspectAppLayout(root)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -75,7 +77,7 @@ func (switcher *Switcher) Switch(root string) error {
|
||||
}
|
||||
|
||||
record := newTransaction(phasePrepared, state.current)
|
||||
if err := writeTransaction(layout, record); err != nil {
|
||||
if err := writeTransactionWithFence(layout, record, fence); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := switcher.runStep(stepPrepared); err != nil {
|
||||
@@ -83,7 +85,13 @@ func (switcher *Switcher) Switch(root string) error {
|
||||
}
|
||||
|
||||
if state.current {
|
||||
if err := os.Rename(layout.current, layout.backup); err != nil {
|
||||
if err := renameManagedDirectory(
|
||||
layout,
|
||||
layout.current,
|
||||
layout.backup,
|
||||
fence,
|
||||
"app root after current backup rename",
|
||||
); err != nil {
|
||||
return fmt.Errorf("backup current directory: %w", err)
|
||||
}
|
||||
if err := switcher.runStep(stepCurrentRenamed); err != nil {
|
||||
@@ -91,21 +99,27 @@ func (switcher *Switcher) Switch(root string) error {
|
||||
}
|
||||
}
|
||||
record.Phase = phaseCurrentBackedUp
|
||||
if err := writeTransaction(layout, record); err != nil {
|
||||
if err := writeTransactionWithFence(layout, record, fence); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := switcher.runStep(stepCurrentBackedUp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Rename(layout.staging, layout.current); err != nil {
|
||||
if err := renameManagedDirectory(
|
||||
layout,
|
||||
layout.staging,
|
||||
layout.current,
|
||||
fence,
|
||||
"app root after staging activation rename",
|
||||
); err != nil {
|
||||
return fmt.Errorf("activate staging directory: %w", err)
|
||||
}
|
||||
if err := switcher.runStep(stepStagingRenamed); err != nil {
|
||||
return err
|
||||
}
|
||||
record.Phase = phaseStagingActivated
|
||||
if err := writeTransaction(layout, record); err != nil {
|
||||
if err := writeTransactionWithFence(layout, record, fence); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := switcher.runStep(stepStagingActivated); err != nil {
|
||||
@@ -115,35 +129,35 @@ func (switcher *Switcher) Switch(root string) error {
|
||||
healthErr := switcher.health(layout.current)
|
||||
if healthErr != nil {
|
||||
record.Phase = phaseRollbackRequired
|
||||
if err := writeTransaction(layout, record); err != nil {
|
||||
if err := writeTransactionWithFence(layout, record, fence); err != nil {
|
||||
return &RollbackError{Health: healthErr, Rollback: err}
|
||||
}
|
||||
if err := switcher.runStep(stepRollbackRequired); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := rollbackActivated(layout, record.HadCurrent); err != nil {
|
||||
if err := rollbackActivated(layout, record.HadCurrent, fence); err != nil {
|
||||
return &RollbackError{Health: healthErr, Rollback: err}
|
||||
}
|
||||
if err := removeTransaction(layout); err != nil {
|
||||
if err := removeTransactionWithFence(layout, fence); err != nil {
|
||||
return &RollbackError{Health: healthErr, Rollback: err}
|
||||
}
|
||||
return fmt.Errorf("%w: %v", ErrHealthCheckFailed, healthErr)
|
||||
}
|
||||
|
||||
record.Phase = phaseCommitted
|
||||
if err := writeTransaction(layout, record); err != nil {
|
||||
if err := writeTransactionWithFence(layout, record, fence); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := switcher.runStep(stepCommitted); err != nil {
|
||||
return err
|
||||
}
|
||||
if record.HadCurrent {
|
||||
if err := removeManagedDirectory(layout, layout.backup); err != nil {
|
||||
return fmt.Errorf("%w: cleanup committed backup: %v", ErrRecoveryRequired, err)
|
||||
if err := removeManagedDirectoryWithFence(layout, layout.backup, fence); err != nil {
|
||||
return fmt.Errorf("%w: cleanup committed backup: %w", ErrRecoveryRequired, err)
|
||||
}
|
||||
}
|
||||
if err := removeTransaction(layout); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrRecoveryRequired, err)
|
||||
if err := removeTransactionWithFence(layout, fence); err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrRecoveryRequired, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -155,7 +169,7 @@ func (switcher *Switcher) runStep(step switchStep) error {
|
||||
return switcher.afterStep(step)
|
||||
}
|
||||
|
||||
func rollbackActivated(layout appLayout, hadCurrent bool) error {
|
||||
func rollbackActivated(layout appLayout, hadCurrent bool, fence durabilityFence) error {
|
||||
state, err := inspectDirectories(layout)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -168,17 +182,35 @@ func rollbackActivated(layout appLayout, hadCurrent bool) error {
|
||||
if state.staging {
|
||||
return fmt.Errorf("%w: current and staging both exist", ErrRollbackFailed)
|
||||
}
|
||||
if err := os.Rename(layout.current, layout.staging); err != nil {
|
||||
if err := renameManagedDirectory(
|
||||
layout,
|
||||
layout.current,
|
||||
layout.staging,
|
||||
fence,
|
||||
"app root after rollback staging rename",
|
||||
); err != nil {
|
||||
return fmt.Errorf("move failed current aside: %w", err)
|
||||
}
|
||||
}
|
||||
if err := os.Rename(layout.backup, layout.current); err != nil {
|
||||
if err := renameManagedDirectory(
|
||||
layout,
|
||||
layout.backup,
|
||||
layout.current,
|
||||
fence,
|
||||
"app root after rollback current restore",
|
||||
); err != nil {
|
||||
if _, statErr := os.Stat(layout.staging); statErr == nil {
|
||||
_ = os.Rename(layout.staging, layout.current)
|
||||
_ = renameManagedDirectory(
|
||||
layout,
|
||||
layout.staging,
|
||||
layout.current,
|
||||
fence,
|
||||
"app root after rollback restore",
|
||||
)
|
||||
}
|
||||
return fmt.Errorf("restore previous current: %w", err)
|
||||
}
|
||||
return removeManagedDirectory(layout, layout.staging)
|
||||
return removeManagedDirectoryWithFence(layout, layout.staging, fence)
|
||||
}
|
||||
|
||||
if state.backup {
|
||||
@@ -188,9 +220,15 @@ func rollbackActivated(layout appLayout, hadCurrent bool) error {
|
||||
if state.staging {
|
||||
return fmt.Errorf("%w: current and staging both exist", ErrRollbackFailed)
|
||||
}
|
||||
if err := os.Rename(layout.current, layout.staging); err != nil {
|
||||
if err := renameManagedDirectory(
|
||||
layout,
|
||||
layout.current,
|
||||
layout.staging,
|
||||
fence,
|
||||
"app root after initial rollback rename",
|
||||
); err != nil {
|
||||
return fmt.Errorf("move failed initial install aside: %w", err)
|
||||
}
|
||||
}
|
||||
return removeManagedDirectory(layout, layout.staging)
|
||||
return removeManagedDirectoryWithFence(layout, layout.staging, fence)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user