dependencies.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /*
  2. Copyright 2020 Docker Compose CLI authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package compose
  14. import (
  15. "context"
  16. "fmt"
  17. "strings"
  18. "sync"
  19. "github.com/compose-spec/compose-go/types"
  20. "golang.org/x/sync/errgroup"
  21. )
  22. // ServiceStatus indicates the status of a service
  23. type ServiceStatus int
  24. // Services status flags
  25. const (
  26. ServiceStopped ServiceStatus = iota
  27. ServiceStarted
  28. )
  29. type graphTraversalConfig struct {
  30. extremityNodesFn func(*Graph) []*Vertex // leaves or roots
  31. adjacentNodesFn func(*Vertex) []*Vertex // getParents or getChildren
  32. filterAdjacentByStatusFn func(*Graph, string, ServiceStatus) []*Vertex // filterChildren or filterParents
  33. targetServiceStatus ServiceStatus
  34. adjacentServiceStatusToSkip ServiceStatus
  35. }
  36. var (
  37. upDirectionTraversalConfig = graphTraversalConfig{
  38. extremityNodesFn: leaves,
  39. adjacentNodesFn: getParents,
  40. filterAdjacentByStatusFn: filterChildren,
  41. adjacentServiceStatusToSkip: ServiceStopped,
  42. targetServiceStatus: ServiceStarted,
  43. }
  44. downDirectionTraversalConfig = graphTraversalConfig{
  45. extremityNodesFn: roots,
  46. adjacentNodesFn: getChildren,
  47. filterAdjacentByStatusFn: filterParents,
  48. adjacentServiceStatusToSkip: ServiceStarted,
  49. targetServiceStatus: ServiceStopped,
  50. }
  51. )
  52. // InDependencyOrder applies the function to the services of the project taking in account the dependency order
  53. func InDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, types.ServiceConfig) error) error {
  54. return visit(ctx, project, upDirectionTraversalConfig, fn, ServiceStopped)
  55. }
  56. // InReverseDependencyOrder applies the function to the services of the project in reverse order of dependencies
  57. func InReverseDependencyOrder(ctx context.Context, project *types.Project, fn func(context.Context, types.ServiceConfig) error) error {
  58. return visit(ctx, project, downDirectionTraversalConfig, fn, ServiceStarted)
  59. }
  60. func visit(ctx context.Context, project *types.Project, traversalConfig graphTraversalConfig, fn func(context.Context, types.ServiceConfig) error, initialStatus ServiceStatus) error {
  61. g := NewGraph(project.Services, initialStatus)
  62. if b, err := g.HasCycles(); b {
  63. return err
  64. }
  65. nodes := traversalConfig.extremityNodesFn(g)
  66. eg, _ := errgroup.WithContext(ctx)
  67. eg.Go(func() error {
  68. return run(ctx, g, eg, nodes, traversalConfig, fn)
  69. })
  70. return eg.Wait()
  71. }
  72. // Note: this could be `graph.walk` or whatever
  73. func run(ctx context.Context, graph *Graph, eg *errgroup.Group, nodes []*Vertex, traversalConfig graphTraversalConfig, fn func(context.Context, types.ServiceConfig) error) error {
  74. for _, node := range nodes {
  75. n := node
  76. // Don't start this service yet if all of its children have
  77. // not been started yet.
  78. if len(traversalConfig.filterAdjacentByStatusFn(graph, n.Service.Name, traversalConfig.adjacentServiceStatusToSkip)) != 0 {
  79. continue
  80. }
  81. eg.Go(func() error {
  82. err := fn(ctx, n.Service)
  83. if err != nil {
  84. return err
  85. }
  86. graph.UpdateStatus(n.Service.Name, traversalConfig.targetServiceStatus)
  87. return run(ctx, graph, eg, traversalConfig.adjacentNodesFn(n), traversalConfig, fn)
  88. })
  89. }
  90. return nil
  91. }
  92. // Graph represents project as service dependencies
  93. type Graph struct {
  94. Vertices map[string]*Vertex
  95. lock sync.RWMutex
  96. }
  97. // Vertex represents a service in the dependencies structure
  98. type Vertex struct {
  99. Key string
  100. Service types.ServiceConfig
  101. Status ServiceStatus
  102. Children map[string]*Vertex
  103. Parents map[string]*Vertex
  104. }
  105. func getParents(v *Vertex) []*Vertex {
  106. return v.GetParents()
  107. }
  108. // GetParents returns a slice with the parent vertexes of the a Vertex
  109. func (v *Vertex) GetParents() []*Vertex {
  110. var res []*Vertex
  111. for _, p := range v.Parents {
  112. res = append(res, p)
  113. }
  114. return res
  115. }
  116. func getChildren(v *Vertex) []*Vertex {
  117. return v.GetChildren()
  118. }
  119. // GetChildren returns a slice with the child vertexes of the a Vertex
  120. func (v *Vertex) GetChildren() []*Vertex {
  121. var res []*Vertex
  122. for _, p := range v.Children {
  123. res = append(res, p)
  124. }
  125. return res
  126. }
  127. // NewGraph returns the dependency graph of the services
  128. func NewGraph(services types.Services, initialStatus ServiceStatus) *Graph {
  129. graph := &Graph{
  130. lock: sync.RWMutex{},
  131. Vertices: map[string]*Vertex{},
  132. }
  133. for _, s := range services {
  134. graph.AddVertex(s.Name, s, initialStatus)
  135. }
  136. for _, s := range services {
  137. for _, name := range s.GetDependencies() {
  138. _ = graph.AddEdge(s.Name, name)
  139. }
  140. }
  141. return graph
  142. }
  143. // NewVertex is the constructor function for the Vertex
  144. func NewVertex(key string, service types.ServiceConfig, initialStatus ServiceStatus) *Vertex {
  145. return &Vertex{
  146. Key: key,
  147. Service: service,
  148. Status: initialStatus,
  149. Parents: map[string]*Vertex{},
  150. Children: map[string]*Vertex{},
  151. }
  152. }
  153. // AddVertex adds a vertex to the Graph
  154. func (g *Graph) AddVertex(key string, service types.ServiceConfig, initialStatus ServiceStatus) {
  155. g.lock.Lock()
  156. defer g.lock.Unlock()
  157. v := NewVertex(key, service, initialStatus)
  158. g.Vertices[key] = v
  159. }
  160. // AddEdge adds a relationship of dependency between vertexes `source` and `destination`
  161. func (g *Graph) AddEdge(source string, destination string) error {
  162. g.lock.Lock()
  163. defer g.lock.Unlock()
  164. sourceVertex := g.Vertices[source]
  165. destinationVertex := g.Vertices[destination]
  166. if sourceVertex == nil {
  167. return fmt.Errorf("could not find %s", source)
  168. }
  169. if destinationVertex == nil {
  170. return fmt.Errorf("could not find %s", destination)
  171. }
  172. // If they are already connected
  173. if _, ok := sourceVertex.Children[destination]; ok {
  174. return nil
  175. }
  176. sourceVertex.Children[destination] = destinationVertex
  177. destinationVertex.Parents[source] = sourceVertex
  178. return nil
  179. }
  180. func leaves(g *Graph) []*Vertex {
  181. return g.Leaves()
  182. }
  183. // Leaves returns the slice of leaves of the graph
  184. func (g *Graph) Leaves() []*Vertex {
  185. g.lock.Lock()
  186. defer g.lock.Unlock()
  187. var res []*Vertex
  188. for _, v := range g.Vertices {
  189. if len(v.Children) == 0 {
  190. res = append(res, v)
  191. }
  192. }
  193. return res
  194. }
  195. func roots(g *Graph) []*Vertex {
  196. return g.Roots()
  197. }
  198. // Roots returns the slice of "Roots" of the graph
  199. func (g *Graph) Roots() []*Vertex {
  200. g.lock.Lock()
  201. defer g.lock.Unlock()
  202. var res []*Vertex
  203. for _, v := range g.Vertices {
  204. if len(v.Parents) == 0 {
  205. res = append(res, v)
  206. }
  207. }
  208. return res
  209. }
  210. // UpdateStatus updates the status of a certain vertex
  211. func (g *Graph) UpdateStatus(key string, status ServiceStatus) {
  212. g.lock.Lock()
  213. defer g.lock.Unlock()
  214. g.Vertices[key].Status = status
  215. }
  216. func filterChildren(g *Graph, k string, s ServiceStatus) []*Vertex {
  217. return g.FilterChildren(k, s)
  218. }
  219. // FilterChildren returns children of a certain vertex that are in a certain status
  220. func (g *Graph) FilterChildren(key string, status ServiceStatus) []*Vertex {
  221. g.lock.Lock()
  222. defer g.lock.Unlock()
  223. var res []*Vertex
  224. vertex := g.Vertices[key]
  225. for _, child := range vertex.Children {
  226. if child.Status == status {
  227. res = append(res, child)
  228. }
  229. }
  230. return res
  231. }
  232. func filterParents(g *Graph, k string, s ServiceStatus) []*Vertex {
  233. return g.FilterParents(k, s)
  234. }
  235. // FilterParents returns the parents of a certain vertex that are in a certain status
  236. func (g *Graph) FilterParents(key string, status ServiceStatus) []*Vertex {
  237. g.lock.Lock()
  238. defer g.lock.Unlock()
  239. var res []*Vertex
  240. vertex := g.Vertices[key]
  241. for _, parent := range vertex.Parents {
  242. if parent.Status == status {
  243. res = append(res, parent)
  244. }
  245. }
  246. return res
  247. }
  248. // HasCycles detects cycles in the graph
  249. func (g *Graph) HasCycles() (bool, error) {
  250. discovered := []string{}
  251. finished := []string{}
  252. for _, vertex := range g.Vertices {
  253. path := []string{
  254. vertex.Key,
  255. }
  256. if !contains(discovered, vertex.Key) && !contains(finished, vertex.Key) {
  257. var err error
  258. discovered, finished, err = g.visit(vertex.Key, path, discovered, finished)
  259. if err != nil {
  260. return true, err
  261. }
  262. }
  263. }
  264. return false, nil
  265. }
  266. func (g *Graph) visit(key string, path []string, discovered []string, finished []string) ([]string, []string, error) {
  267. discovered = append(discovered, key)
  268. for _, v := range g.Vertices[key].Children {
  269. path := append(path, v.Key)
  270. if contains(discovered, v.Key) {
  271. return nil, nil, fmt.Errorf("cycle found: %s", strings.Join(path, " -> "))
  272. }
  273. if !contains(finished, v.Key) {
  274. if _, _, err := g.visit(v.Key, path, discovered, finished); err != nil {
  275. return nil, nil, err
  276. }
  277. }
  278. }
  279. discovered = remove(discovered, key)
  280. finished = append(finished, key)
  281. return discovered, finished, nil
  282. }
  283. func remove(slice []string, item string) []string {
  284. var s []string
  285. for _, i := range slice {
  286. if i != item {
  287. s = append(s, i)
  288. }
  289. }
  290. return s
  291. }