diff --git a/cmd/create.go b/cmd/create.go index ed0fa29..59af97e 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -38,8 +38,7 @@ var createCmd = &cobra.Command{ names or categories. Multiple arguments are supported.`, Run: func(_ *cobra.Command, args []string) { conts := config.Union(args, contMask) - container.Reorder(conts, container.Start) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.CreateCommands() }) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.CreateCommands() }, 0) }, } diff --git a/cmd/execute.go b/cmd/execute.go index 35b44c6..c14abd7 100644 --- a/cmd/execute.go +++ b/cmd/execute.go @@ -29,27 +29,81 @@ import ( "gitea.elkins.co/Networking/ccl/internal/pkg/command" "gitea.elkins.co/Networking/ccl/internal/pkg/container" log "github.com/sirupsen/logrus" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" ) -func execForEach(tgts []container.Container, getSet func(*container.Container) command.CommandSet) { - var wg sync.WaitGroup +type runner struct{ + GetSet func(*container.Container) command.CommandSet + GroupScale int +} + +func execSetsForEachGroup(tgts [] container.Container, runners []runner) { var ser sync.Mutex // serialize non-async containers + runLevel := make(map[int][]container.Container) + for i := range tgts { - async := tgts[i].StartOrder.ValueOrZero() == 0 - wg.Add(1) - if async { - // TODO: need to log errors on this branch - go runSet(&tgts[i], getSet, &wg) - } else { - ser.Lock() - go func(cont *container.Container, ser *sync.Mutex) { - defer ser.Unlock() - runSet(cont, getSet, &wg) - time.Sleep(5 * time.Second) - }(&tgts[i], &ser) - } + rl := tgts[i].StartGroup + runLevel[rl] = append(runLevel[rl], tgts[i]) } - wg.Wait() + + rls := maps.Keys(runLevel) + slices.Sort(rls) + + for _, r := range rls { + cs := runLevel[r] + ser.Lock() + fmt.Fprintln(output, "*** Running a command set for group", r, mapNames(cs)) + go func(async bool, conts []container.Container, ser *sync.Mutex) { + var wg sync.WaitGroup + defer func() { + wg.Wait() + ser.Unlock() + }() + for i := range conts { + wg.Add(1) + go runSet(&conts[i], getSet, &wg) + } + if !async { + time.Sleep(2 * time.Second) + } + }(r == 0, cs, &ser) + } + ser.Lock() +} + +func execForEach(tgts []container.Container, getSet func(*container.Container) command.CommandSet, groupScale int) { + var ser sync.Mutex // serialize non-async containers + runLevel := make(map[int][]container.Container) + + for i := range tgts { + rl := int(tgts[i].StartGroup * groupScale) + runLevel[rl] = append(runLevel[rl], tgts[i]) + } + + rls := maps.Keys(runLevel) + slices.Sort(rls) + + for _, r := range rls { + cs := runLevel[r] + ser.Lock() + fmt.Fprintln(output, "*** Running a command set for group", r, mapNames(cs)) + go func(async bool, conts []container.Container, ser *sync.Mutex) { + var wg sync.WaitGroup + defer func() { + wg.Wait() + ser.Unlock() + }() + for i := range conts { + wg.Add(1) + go runSet(&conts[i], getSet, &wg) + } + if !async { + time.Sleep(2 * time.Second) + } + }(r == 0, cs, &ser) + } + ser.Lock() } func runSet(cont *container.Container, getSet func(*container.Container) command.CommandSet, wg *sync.WaitGroup) { @@ -67,11 +121,11 @@ func runSet(cont *container.Container, getSet func(*container.Container) command } // For debugging -func printConts(conts []container.Container) { +func mapNames(conts []container.Container) string { names := []string{} for i := range conts { - names = append(names, fmt.Sprintf("[%s %d]", conts[i].Name, conts[i].StartOrder.ValueOrZero())) + names = append(names, conts[i].Name) } - fmt.Printf("%v\n", names) + return fmt.Sprintf("%v", names) } diff --git a/cmd/pull.go b/cmd/pull.go index 4366a9c..0acc301 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -39,8 +39,7 @@ affected: the old image will still remain, though untagged, and any defined cont will still use it.`, Run: func(cmd *cobra.Command, args []string) { conts := config.Union(args, contMask) - container.Reorder(conts, container.Start) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.PullCommands() }) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.PullCommands() }, 0) }, } diff --git a/cmd/recreate.go b/cmd/recreate.go index a2f9943..24136be 100644 --- a/cmd/recreate.go +++ b/cmd/recreate.go @@ -38,11 +38,9 @@ var recreateCmd = &cobra.Command{ one or more container names or categories. If empty, "all" is assumed.`, Run: func(_ *cobra.Command, args []string) { conts := config.Union(args, contMask) - container.Reorder(conts, container.Stop) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }) - container.Reorder(conts, container.Start) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.RecreateCommands() }) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.ConditionalStartCommands() }) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }, -1) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.RecreateCommands() }, 0) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.ConditionalStartCommands() }, 1) }, } diff --git a/cmd/restart.go b/cmd/restart.go index 24cb0ed..642cfa7 100644 --- a/cmd/restart.go +++ b/cmd/restart.go @@ -38,10 +38,8 @@ var restartCmd = &cobra.Command{ one or more container names or categories. If empty, "all" is assumed.`, Run: func(_ *cobra.Command, args []string) { conts := config.Union(args, contMask) - container.Reorder(conts, container.Stop) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }) - container.Reorder(conts, container.Start) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.StartCommands() }) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }, -1) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.StartCommands() }, 1) }, } diff --git a/cmd/rm.go b/cmd/rm.go index a2ff0f4..97b5996 100644 --- a/cmd/rm.go +++ b/cmd/rm.go @@ -39,9 +39,8 @@ var rmCmd = &cobra.Command{ If running, they will first be stopped.`, Run: func(cmd *cobra.Command, args []string) { conts := config.Union(args, contMask) - container.Reorder(conts, container.Stop) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.RemoveCommands() }) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }, -1) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.RemoveCommands() }, 0) }, } diff --git a/cmd/start.go b/cmd/start.go index 123c0ef..946c1a0 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -38,8 +38,7 @@ var startCmd = &cobra.Command{ one or more container names or categories. If empty, "all" is assumed.`, Run: func(_ *cobra.Command, args []string) { conts := config.Union(args, contMask) - container.Reorder(conts, container.Start) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.StartCommands() }) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.StartCommands() }, 1) }, } diff --git a/cmd/stop.go b/cmd/stop.go index ddb51c8..cbbca0c 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -38,8 +38,7 @@ var stopCmd = &cobra.Command{ one or more container names or categories. If empty, "all" is assumed.`, Run: func(_ *cobra.Command, args []string) { conts := config.Union(args, contMask) - container.Reorder(conts, container.Stop) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }, -1) }, } diff --git a/cmd/update.go b/cmd/update.go index f6d945a..db0fd10 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -38,10 +38,8 @@ var updateCmd = &cobra.Command{ one or more container names or categories. If empty, "all" is assumed.`, Run: func(_ *cobra.Command, args []string) { conts := config.Union(args, contMask) - container.Reorder(conts, container.Stop) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.UpdateCommands() }) - container.Reorder(conts, container.Start) - execForEach(conts, func(c *container.Container) command.CommandSet { return c.ConditionalStartCommands() }) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.UpdateCommands() }, -1) + execForEach(conts, func(c *container.Container) command.CommandSet { return c.ConditionalStartCommands() }, 1) }, } diff --git a/internal/pkg/container/container.go b/internal/pkg/container/container.go index 58f114e..d247bca 100644 --- a/internal/pkg/container/container.go +++ b/internal/pkg/container/container.go @@ -57,10 +57,10 @@ type Container struct { ExposeUdp []uint16 `toml:"expose_udp,omitempty"` PortsTcp map[uint16]uint16 `toml:"ports,omitempty"` NetNS string `toml:"netns,omitempty"` - StartOrder null.Int `toml:"start_order,omitempty"` + StartGroup int `toml:"group,omitempty"` - conn context.Context - cdata *define.InspectContainerData + conn context.Context + cdata *define.InspectContainerData wasRunning null.Bool } @@ -326,7 +326,7 @@ func (c *Container) UpdateCommands() cmd.CommandSet { }) } -func (c *Container) ConditionalStartCommands() cmd.CommandSet { +func (c *Container) ConditionalStartCommands() cmd.CommandSet { return c.newCommandSet("CONDSTART", cmd.Commands{ cmd.NewConditional("restart_if_was_running", c.wasRunning.ValueOrZero, diff --git a/internal/pkg/container/ordering.go b/internal/pkg/container/ordering.go deleted file mode 100644 index b1d1549..0000000 --- a/internal/pkg/container/ordering.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright © 2022 Joel D. Elkins - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ - -package container - -import "golang.org/x/exp/slices" - -type operation bool - -const ( - Start operation = true - Stop operation = false -) - -func Reorder(conts []Container, op operation) { - // null orderings go first for either start or stop, as they are executed asynchronously - norm := func(a, b Container) bool { - return a.StartOrder.ValueOrZero() < b.StartOrder.ValueOrZero() - } - rev := func(a, b Container) bool { - if a.StartOrder.ValueOrZero() == 0 { - return true - } - if b.StartOrder.ValueOrZero() == 0 { - return false - } - return norm(b, a) - } - var sorter func(a, b Container) bool - if op == Start { - sorter = norm - } else { - sorter = rev - } - slices.SortFunc(conts, sorter) -}