mirror of
https://github.com/akvorado/akvorado.git
synced 2025-12-12 06:24:10 +01:00
common/daemon: add a method to reexec itself
This commit is contained in:
@@ -39,6 +39,7 @@ func StartStopComponents(r *reporter.Reporter, daemonComponent daemon.Component,
|
|||||||
|
|
||||||
<-daemonComponent.Terminated()
|
<-daemonComponent.Terminated()
|
||||||
r.Info().Msg("stopping all components")
|
r.Info().Msg("stopping all components")
|
||||||
|
daemonComponent.FinishReexec()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ package daemon
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"gopkg.in/tomb.v2"
|
"gopkg.in/tomb.v2"
|
||||||
@@ -20,6 +21,8 @@ import (
|
|||||||
type Component interface {
|
type Component interface {
|
||||||
Start() error
|
Start() error
|
||||||
Stop() error
|
Stop() error
|
||||||
|
Reexec()
|
||||||
|
FinishReexec()
|
||||||
Track(t *tomb.Tomb, who string)
|
Track(t *tomb.Tomb, who string)
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
@@ -30,8 +33,9 @@ type Component interface {
|
|||||||
// realComponent is a non-mock implementation of the Component
|
// realComponent is a non-mock implementation of the Component
|
||||||
// interface.
|
// interface.
|
||||||
type realComponent struct {
|
type realComponent struct {
|
||||||
r *reporter.Reporter
|
r *reporter.Reporter
|
||||||
tombs []tombWithOrigin
|
tombs []tombWithOrigin
|
||||||
|
shouldReexec atomic.Bool
|
||||||
|
|
||||||
lifecycleComponent
|
lifecycleComponent
|
||||||
}
|
}
|
||||||
@@ -70,11 +74,12 @@ func (c *realComponent) Start() error {
|
|||||||
c.Terminate()
|
c.Terminate()
|
||||||
}(t)
|
}(t)
|
||||||
}
|
}
|
||||||
// On signal, terminate
|
// On signal, terminate or reexec
|
||||||
go func() {
|
go func() {
|
||||||
signals := make(chan os.Signal, 1)
|
signals := make(chan os.Signal, 1)
|
||||||
signal.Notify(signals,
|
signal.Notify(signals,
|
||||||
syscall.SIGINT, syscall.SIGTERM)
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM)
|
||||||
select {
|
select {
|
||||||
case s := <-signals:
|
case s := <-signals:
|
||||||
c.r.Debug().Stringer("signal", s).Msg("signal received")
|
c.r.Debug().Stringer("signal", s).Msg("signal received")
|
||||||
@@ -97,6 +102,30 @@ func (c *realComponent) Stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reexec will reexecute the current process with the same arguments.
|
||||||
|
func (c *realComponent) Reexec() {
|
||||||
|
c.shouldReexec.Store(true)
|
||||||
|
c.Terminate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishReexec should be called just before exiting to trigger the real reexec.
|
||||||
|
func (c *realComponent) FinishReexec() {
|
||||||
|
if c.shouldReexec.Load() {
|
||||||
|
executable, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
c.r.Err(err).Msg("cannot get executable name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
env := os.Environ()
|
||||||
|
args := append([]string{executable}, os.Args[1:]...)
|
||||||
|
c.r.Info().Strs("args", args).Msg("reexec in progress")
|
||||||
|
if err := syscall.Exec(executable, args, env); err != nil {
|
||||||
|
c.r.Err(err).Msg("cannot reexec")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add a new tomb to be tracked. This is only used before Start().
|
// Add a new tomb to be tracked. This is only used before Start().
|
||||||
func (c *realComponent) Track(t *tomb.Tomb, who string) {
|
func (c *realComponent) Track(t *tomb.Tomb, who string) {
|
||||||
c.tombs = append(c.tombs, tombWithOrigin{
|
c.tombs = append(c.tombs, tombWithOrigin{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package daemon
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/synctest"
|
"testing/synctest"
|
||||||
@@ -62,6 +63,28 @@ func TestTerminateWithSignal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReexecWithSignal(t *testing.T) {
|
||||||
|
|
||||||
|
r := reporter.NewMock(t)
|
||||||
|
c, err := New(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New() error:\n%+v", err)
|
||||||
|
}
|
||||||
|
helpers.StartStop(t, c)
|
||||||
|
|
||||||
|
c.Reexec()
|
||||||
|
if os.Getenv("TEST_DAEMON_REEXEC") == "1" {
|
||||||
|
// This is a way to increase a bit coverage
|
||||||
|
executable, _ := os.Executable()
|
||||||
|
os.Remove(executable)
|
||||||
|
c.FinishReexec()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Setenv("TEST_DAEMON_REEXEC", "1")
|
||||||
|
c.FinishReexec()
|
||||||
|
t.Fatalf("No reexec done!")
|
||||||
|
}
|
||||||
|
|
||||||
func TestStop(t *testing.T) {
|
func TestStop(t *testing.T) {
|
||||||
r := reporter.NewMock(t)
|
r := reporter.NewMock(t)
|
||||||
c, err := New(r)
|
c, err := New(r)
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ type MockComponent struct {
|
|||||||
lifecycleComponent
|
lifecycleComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Component = &MockComponent{}
|
||||||
|
|
||||||
// NewMock will create a daemon component that does nothing.
|
// NewMock will create a daemon component that does nothing.
|
||||||
func NewMock(t testing.TB) Component {
|
func NewMock(t testing.TB) Component {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -38,6 +40,14 @@ func (c *MockComponent) Stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reexec does nothing for the mock implementation
|
||||||
|
func (c *MockComponent) Reexec() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinishReexec does nothing for the mock implementation
|
||||||
|
func (c *MockComponent) FinishReexec() {
|
||||||
|
}
|
||||||
|
|
||||||
// Track does nothing
|
// Track does nothing
|
||||||
func (c *MockComponent) Track(_ *tomb.Tomb, _ string) {
|
func (c *MockComponent) Track(_ *tomb.Tomb, _ string) {
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user