Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plan): use lanes to start services with dependencies #437

61 changes: 38 additions & 23 deletions internals/daemon/api_services.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2014-2020 Canonical Ltd
// Copyright (c) 2014-2024 Canonical Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as
Expand Down Expand Up @@ -104,37 +104,38 @@ func v1PostServices(c *Command, r *http.Request, _ *UserState) Response {
defer st.Unlock()

var taskSet *state.TaskSet
var lanes [][]string
var services []string
switch payload.Action {
case "start", "autostart":
services, err = servmgr.StartOrder(payload.Services)
lanes, err = servmgr.StartOrder(payload.Services)
if err != nil {
break
}
taskSet, err = servstate.Start(st, services)
taskSet, err = servstate.Start(st, lanes)
case "stop":
services, err = servmgr.StopOrder(payload.Services)
lanes, err = servmgr.StopOrder(payload.Services)
if err != nil {
break
}
taskSet, err = servstate.Stop(st, services)
taskSet, err = servstate.Stop(st, lanes)
case "restart":
services, err = servmgr.StopOrder(payload.Services)
lanes, err = servmgr.StopOrder(payload.Services)
if err != nil {
break
}
services = intersectOrdered(payload.Services, services)
lanes = intersectOrdered(payload.Services, lanes)
var stopTasks *state.TaskSet
stopTasks, err = servstate.Stop(st, services)
stopTasks, err = servstate.Stop(st, lanes)
if err != nil {
break
}
services, err = servmgr.StartOrder(payload.Services)
lanes, err = servmgr.StartOrder(payload.Services)
if err != nil {
break
}
var startTasks *state.TaskSet
startTasks, err = servstate.Start(st, services)
startTasks, err = servstate.Start(st, lanes)
if err != nil {
break
}
Expand All @@ -143,18 +144,18 @@ func v1PostServices(c *Command, r *http.Request, _ *UserState) Response {
taskSet.AddAll(stopTasks)
taskSet.AddAll(startTasks)
case "replan":
var stopNames, startNames []string
stopNames, startNames, err = servmgr.Replan()
var stopLanes, startLanes [][]string
stopLanes, startLanes, err = servmgr.Replan()
if err != nil {
break
}
var stopTasks *state.TaskSet
stopTasks, err = servstate.Stop(st, stopNames)
stopTasks, err = servstate.Stop(st, stopLanes)
if err != nil {
break
}
var startTasks *state.TaskSet
startTasks, err = servstate.Start(st, startNames)
startTasks, err = servstate.Start(st, startLanes)
if err != nil {
break
}
Expand All @@ -165,11 +166,15 @@ func v1PostServices(c *Command, r *http.Request, _ *UserState) Response {

// Populate a list of services affected by the replan for summary.
replanned := make(map[string]bool)
for _, v := range stopNames {
replanned[v] = true
for _, lane := range stopLanes {
for _, v := range lane {
replanned[v] = true
}
}
for _, v := range startNames {
replanned[v] = true
for _, lane := range startLanes {
for _, v := range lane {
replanned[v] = true
}
}
for k := range replanned {
services = append(services, k)
Expand All @@ -186,6 +191,9 @@ func v1PostServices(c *Command, r *http.Request, _ *UserState) Response {
// Use the original requested service name for the summary, not the
// resolved one. But do use the resolved set for the count.
var summary string
for _, row := range lanes {
services = append(services, row...)
}
switch {
case len(taskSet.Tasks()) == 0:
// Can happen with a replan that has no services to stop/start. A
Expand Down Expand Up @@ -222,15 +230,22 @@ func v1PostService(c *Command, r *http.Request, _ *UserState) Response {

// intersectOrdered returns the intersection of left and right where
// the right's ordering is persisted in the resulting set.
func intersectOrdered(left []string, orderedRight []string) []string {
func intersectOrdered(left []string, orderedRight [][]string) [][]string {
m := map[string]bool{}
for _, v := range left {
m[v] = true
}
var out []string
for _, v := range orderedRight {
if m[v] {
out = append(out, v)

var out [][]string
for _, lane := range orderedRight {
var intersectLane []string
for _, v := range lane {
if m[v] {
intersectLane = append(intersectLane, v)
}
}
if len(intersectLane) > 0 {
out = append(out, intersectLane)
}
}
return out
Expand Down
54 changes: 35 additions & 19 deletions internals/overlord/servstate/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,19 +204,28 @@ func (m *ServiceManager) DefaultServiceNames() ([]string, error) {
}
}

return currentPlan.StartOrder(names)
lanes, err := currentPlan.StartOrder(names)
if err != nil {
return nil, err
}

var result []string
for _, lane := range lanes {
result = append(result, lane...)
}
return result, err
}

// StartOrder returns the provided services, together with any required
// dependencies, in the proper order for starting them all up.
func (m *ServiceManager) StartOrder(services []string) ([]string, error) {
// dependencies, in the proper order, put in lanes, for starting them all up.
func (m *ServiceManager) StartOrder(services []string) ([][]string, error) {
currentPlan := m.getPlan()
return currentPlan.StartOrder(services)
}

// StopOrder returns the provided services, together with any dependants,
// in the proper order for stopping them all.
func (m *ServiceManager) StopOrder(services []string) ([]string, error) {
// in the proper order, put in lanes, for stopping them all.
func (m *ServiceManager) StopOrder(services []string) ([][]string, error) {
currentPlan := m.getPlan()
return currentPlan.StopOrder(services)
}
Expand Down Expand Up @@ -251,9 +260,9 @@ func (m *ServiceManager) ServiceLogs(services []string, last int) (map[string]se
return iterators, nil
}

// Replan returns a list of services to stop and services to start because
// their plans had changed between when they started and this call.
func (m *ServiceManager) Replan() ([]string, []string, error) {
// Replan returns a list of services in lanes to stop and services to start
// because their plans had changed between when they started and this call.
func (m *ServiceManager) Replan() ([][]string, [][]string, error) {
currentPlan := m.getPlan()
m.servicesLock.Lock()
defer m.servicesLock.Unlock()
Expand All @@ -278,7 +287,7 @@ func (m *ServiceManager) Replan() ([]string, []string, error) {
}
}

stop, err := currentPlan.StopOrder(stop)
stopLanes, err := currentPlan.StopOrder(stop)
if err != nil {
return nil, nil, err
}
Expand All @@ -288,12 +297,12 @@ func (m *ServiceManager) Replan() ([]string, []string, error) {
}
}

start, err = currentPlan.StartOrder(start)
startLanes, err := currentPlan.StartOrder(start)
if err != nil {
return nil, nil, err
}

return stop, start, nil
return stopLanes, startLanes, nil
}

func (m *ServiceManager) SendSignal(services []string, signal string) error {
Expand Down Expand Up @@ -342,8 +351,9 @@ func (m *ServiceManager) CheckFailed(name string) {
// exit. If it starts just before, it would continue to run after the service
// manager is terminated. If it starts just after (before the main process
// exits), it would generate a runtime error as the reaper would already be dead.
// This function returns a slice of service names to stop, in dependency order.
func servicesToStop(m *ServiceManager) ([]string, error) {
// This function returns a slice of service names to stop, in dependency order,
// put in lanes.
func servicesToStop(m *ServiceManager) ([][]string, error) {
currentPlan := m.getPlan()
// Get all service names in plan.
services := make([]string, 0, len(currentPlan.Services))
Expand All @@ -360,12 +370,18 @@ func servicesToStop(m *ServiceManager) ([]string, error) {
// Filter down to only those that are running or in backoff
m.servicesLock.Lock()
defer m.servicesLock.Unlock()
var notStopped []string
for _, name := range stop {
s := m.services[name]
if s != nil && (s.state == stateRunning || s.state == stateBackoff) {
notStopped = append(notStopped, name)
var result [][]string
for _, services := range stop {
var notStopped []string
for _, name := range services {
s := m.services[name]
if s != nil && (s.state == stateRunning || s.state == stateBackoff) {
notStopped = append(notStopped, name)
}
}
if len(notStopped) > 0 {
result = append(result, notStopped)
}
}
return notStopped, nil
return result, nil
}
Loading
Loading