m/n/c/update: implement Rollback
Implement a mechanism for manual rollbacks, useful for cases where
rolling forward is not an option or automated rollbacks did not catch an
issue. To ensure that the rollback does not break the machine, the
alternate slot is only tried on next boot and that version needs to set
the slot active before it is permanently activated.
Change-Id: I2fe4dfedcecd5bf7d1bdebdd070e40e817bca7c3
Reviewed-on: https://review.monogon.dev/c/monogon/+/3386
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/core/update/update.go b/metropolis/node/core/update/update.go
index b6d2ce4..e4fbbbd 100644
--- a/metropolis/node/core/update/update.go
+++ b/metropolis/node/core/update/update.go
@@ -163,6 +163,32 @@
return nil
}
+// Rollback sets the currently-inactive slot as the next boot slot. This is
+// intended to recover from scenarios where roll-forward fixing is difficult.
+// Only the next boot slot is set to make sure that the node is not
+// made unbootable accidentally. On successful bootup that code can switch the
+// active slot to itself.
+func (s *Service) Rollback() error {
+ if s.ESPPath == "" {
+ return errors.New("no ESP information provided to update service, cannot continue")
+ }
+ activeSlot := s.CurrentlyRunningSlot()
+ abState, err := s.getABState()
+ if err != nil {
+ return fmt.Errorf("no valid A/B loader state, cannot rollback: %w", err)
+ }
+ nextSlot := activeSlot.Other()
+ err = s.setABState(&abloaderpb.ABLoaderData{
+ ActiveSlot: abState.ActiveSlot,
+ NextSlot: abloaderpb.Slot(nextSlot),
+ })
+ if err != nil {
+ return fmt.Errorf("while setting next A/B slot: %w", err)
+ }
+ s.Logger.Warningf("Rollback requested, NextSlot set to %v", nextSlot)
+ return nil
+}
+
func openSystemSlot(slot Slot) (*blockdev.Device, error) {
switch slot {
case SlotA: