Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cmd/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,18 @@ func run(appCallback *AppCallback, stopCh chan struct{}) {
os.Exit(0)
}

// On Merlin, self-upgrade replaces the executable before the old process
// invokes Service.Start. The newly launched `ctrld run` process is therefore
// the first guaranteed execution point for new service-lifecycle code.
// Migrate an exact ctrld-owned legacy startup script here. Failure is
// deliberately non-fatal: the legacy script remains atomically intact and
// the next daemon start retries the migration.
if router.Name() == "merlin" {
if err := router.RefreshMerlinStartupScriptFromRunningBinary(); err != nil {
mainLog.Load().Warn().Err(err).Msg("could not refresh Merlin startup script")
}
}

p.onStarted = append(p.onStarted, func() {
for _, lc := range p.cfg.Listener {
if shouldAllocateLoopbackIP(lc.IP) {
Expand Down
162 changes: 117 additions & 45 deletions cmd/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,19 @@ func initStopCmd() *cobra.Command {
return stopCmd
}


type restartWithBetweenService interface {
RestartWith(func() error) error
}

func restartServiceTransaction(s service.Service, between func() error) (bool, error) {
tx, ok := s.(restartWithBetweenService)
if !ok {
return false, nil
}
return true, tx.RestartWith(between)
}

func initRestartCmd() *cobra.Command {
restartCmd := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
Expand Down Expand Up @@ -971,49 +984,77 @@ func initRestartCmd() *cobra.Command {
}

doRestart := func() bool {
tasks := []task{
{s.Stop, true, "Stop"},
{func() error {
p.router.Cleanup()
// restore static DNS settings or DHCP
p.resetDNS(false, true)
return nil
}, false, "Cleanup"},
{func() error {
time.Sleep(time.Second * 1)
return nil
}, false, "Waiting for service to stop"},
}
if doTasks(tasks) {
between := func() error {
p.router.Cleanup()
// restore static DNS settings or DHCP
p.resetDNS(false, true)
time.Sleep(time.Second)

if router.WaitProcessExited() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()

loop:
for {
select {
case <-ctx.Done():
mainLog.Load().Error().Msg("timeout while waiting for service to stop")
break loop
return nil
default:
}
time.Sleep(time.Second)
if status, _ := s.Status(); status == service.StatusStopped {
break
}
time.Sleep(time.Second)
}
}
} else {
return false
return nil
}

tasks = []task{
{s.Start, true, "Start"},
if _, ok := s.(restartWithBetweenService); ok {
return doTasks([]task{
{func() error {
_, err := restartServiceTransaction(s, between)
return err
}, true, "Restart"},
})
}

return doTasks(tasks)
// Preserve the existing restart behavior for non-Merlin service
// backends. Only Merlin opts into RestartWith.
tasks := []task{
{s.Stop, true, "Stop"},
{func() error {
p.router.Cleanup()
p.resetDNS(false, true)
return nil
}, false, "Cleanup"},
{func() error {
time.Sleep(time.Second)
return nil
}, false, "Waiting for service to stop"},
}
if !doTasks(tasks) {
return false
}
if router.WaitProcessExited() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()

loop:
for {
select {
case <-ctx.Done():
mainLog.Load().Error().Msg("timeout while waiting for service to stop")
break loop
default:
}
time.Sleep(time.Second)
if status, _ := s.Status(); status == service.StatusStopped {
break
}
}
}
return doTasks([]task{{s.Start, true, "Start"}})
}

if doRestart() {
Expand Down Expand Up @@ -1547,25 +1588,58 @@ func initUpgradeCmd() *cobra.Command {
if !svcInstalled {
return true
}
tasks := []task{
{s.Stop, true, "Stop"},
{func() error {
p.router.Cleanup()
// restore static DNS settings or DHCP
p.resetDNS(false, true)
return nil
}, false, "Cleanup"},
{func() error {
time.Sleep(time.Second * 1)
return nil
}, false, "Waiting for service to stop"},
}
if doTasks(tasks) {

between := func() error {
p.router.Cleanup()
// restore static DNS settings or DHCP
p.resetDNS(false, true)
time.Sleep(time.Second)

if router.WaitProcessExited() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()

for {
select {
case <-ctx.Done():
mainLog.Load().Error().Msg("timeout while waiting for service to stop")
return nil
default:
}
if status, _ := s.Status(); status == service.StatusStopped {
break
}
time.Sleep(time.Second)
}
}
return nil
}

if _, ok := s.(restartWithBetweenService); ok {
_, err := restartServiceTransaction(s, between)
if err != nil {
mainLog.Load().Error().Err(err).Msg("failed to restart ctrld service")
return false
}
} else {
// Keep the historical upgrade restart path for every non-Merlin
// backend; they do not participate in the new Merlin flock.
tasks := []task{
{s.Stop, true, "Stop"},
{func() error {
p.router.Cleanup()
p.resetDNS(false, true)
return nil
}, false, "Cleanup"},
{func() error {
time.Sleep(time.Second)
return nil
}, false, "Waiting for service to stop"},
}
if doTasks(tasks) && router.WaitProcessExited() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()

loop:
for {
select {
Expand All @@ -1580,17 +1654,15 @@ func initUpgradeCmd() *cobra.Command {
}
}
}
if !doTasks([]task{{s.Start, true, "Start"}}) {
return false
}
}

tasks = []task{
{s.Start, true, "Start"},
}
if doTasks(tasks) {
if dir, err := socketDir(); err == nil {
if cc := newSocketControlClient(context.TODO(), s, dir); cc != nil {
_, _ = cc.post(ifacePath, nil)
return true
}
if dir, err := socketDir(); err == nil {
if cc := newSocketControlClient(context.TODO(), s, dir); cc != nil {
_, _ = cc.post(ifacePath, nil)
return true
}
}
return false
Expand Down
Loading
Loading