Skip to content

Commit

Permalink
feat(daemon): add external reboot mode
Browse files Browse the repository at this point in the history
  • Loading branch information
anpep committed Dec 2, 2024
1 parent 77decbd commit 2fd9aea
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 2 deletions.
19 changes: 17 additions & 2 deletions internals/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ var (
ErrRestartSocket = fmt.Errorf("daemon stop requested to wait for socket activation")
ErrRestartServiceFailure = fmt.Errorf("daemon stop requested due to service failure")
ErrRestartCheckFailure = fmt.Errorf("daemon stop requested due to check failure")
ErrRestartExternal = fmt.Errorf("daemon stop requested due to externally-handled reboot")

systemdSdNotify = systemd.SdNotify
sysGetuid = sys.Getuid
Expand Down Expand Up @@ -508,7 +509,7 @@ func (d *Daemon) HandleRestart(t restart.RestartType) {
case restart.RestartSystem:
// try to schedule a fallback slow reboot already here,
// in case we get stuck shutting down
if err := rebootHandler(rebootWaitTimeout); err != nil {
if err := fallbackRebootHandler(rebootWaitTimeout); err != nil {
logger.Noticef("%s", err)
}
d.mu.Lock()
Expand Down Expand Up @@ -702,7 +703,10 @@ func (d *Daemon) doReboot(sigCh chan<- os.Signal, waitTimeout time.Duration) err

const rebootMsg = "reboot scheduled to update the system"

var rebootHandler = systemdModeReboot
var (
rebootHandler = systemdModeReboot
fallbackRebootHandler = systemdModeReboot
)

type RebootMode int

Expand All @@ -711,6 +715,8 @@ const (
SystemdMode RebootMode = iota + 1
// Reboot uses direct kernel syscalls
SyscallMode
// Reboot is handled externally after the daemon stops
ExternalMode
)

// SetRebootMode configures how the system issues a reboot. The default
Expand All @@ -720,8 +726,13 @@ func SetRebootMode(mode RebootMode) {
switch mode {
case SystemdMode:
rebootHandler = systemdModeReboot
fallbackRebootHandler = systemdModeReboot
case SyscallMode:
rebootHandler = syscallModeReboot
fallbackRebootHandler = syscallModeReboot
case ExternalMode:
rebootHandler = externalModeReboot
fallbackRebootHandler = syscallModeReboot
default:
panic(fmt.Sprintf("unsupported reboot mode %v", mode))
}
Expand Down Expand Up @@ -773,6 +784,10 @@ func syscallModeReboot(rebootDelay time.Duration) error {
return nil
}

func externalModeReboot(rebootDelay time.Duration) error {
return ErrRestartExternal
}

func (d *Daemon) Dying() <-chan struct{} {
return d.tomb.Dying()
}
Expand Down
55 changes: 55 additions & 0 deletions internals/daemon/daemon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package daemon
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net"
Expand Down Expand Up @@ -793,6 +794,7 @@ func (s *daemonSuite) TestRestartSystemWiring(c *C) {
oldRebootWaitTimeout := rebootWaitTimeout
defer func() {
rebootHandler = systemdModeReboot
fallbackRebootHandler = systemdModeReboot
rebootNoticeWait = oldRebootNoticeWait
rebootWaitTimeout = oldRebootWaitTimeout
}()
Expand All @@ -804,6 +806,7 @@ func (s *daemonSuite) TestRestartSystemWiring(c *C) {
delays = append(delays, d)
return nil
}
fallbackRebootHandler = rebootHandler

st.Lock()
restart.Request(st, restart.RestartSystem)
Expand Down Expand Up @@ -1172,6 +1175,58 @@ services:
c.Assert(err, Equals, ErrRestartServiceFailure)
}

func (s *daemonSuite) TestRebootExternal(c *C) {
oldRebootWaitTimeout := rebootWaitTimeout
defer func() {
rebootWaitTimeout = oldRebootWaitTimeout
}()
rebootWaitTimeout = 0

didFallbackReboot := false
defer FakeSyscallSync(func() {})()
defer FakeSyscallReboot(func(cmd int) error {
if cmd == syscall.LINUX_REBOOT_CMD_RESTART {
didFallbackReboot = true
}
return nil
})()
SetRebootMode(ExternalMode)
defer SetRebootMode(SystemdMode)

d := s.newDaemon(c)
makeDaemonListeners(c, d)
c.Assert(d.Start(), IsNil)

st := d.overlord.State()
st.Lock()
restart.Request(st, restart.RestartSystem)
st.Unlock()

select {
case <-d.Dying():
case <-time.After(2 * time.Second):
c.Fatal("RequestRestart -> overlord -> Kill chain didn't work")
}

d.mu.Lock()
restartType := d.requestedRestart
d.mu.Unlock()

c.Assert(restartType, Equals, restart.RestartSystem)

err := d.Stop(nil)
c.Assert(errors.Is(err, ErrRestartExternal), Equals, true)

d.mu.Lock()
d.requestedRestart = restart.RestartUnset
d.mu.Unlock()

c.Assert(didFallbackReboot, Equals, true)
st.Lock()
restart.ClearReboot(st)
st.Unlock()
}

func (s *daemonSuite) TestConnTrackerCanShutdown(c *C) {
ct := &connTracker{conns: make(map[net.Conn]struct{})}
c.Check(ct.CanStandby(), Equals, true)
Expand Down

0 comments on commit 2fd9aea

Please sign in to comment.