refactor: make parallel commands available to all of Dokku

There are other places in the codebase that currently act on all apps or could benefit from being able to do so. Being able to run `common.RunCommandAgainstAllApps()` and get that for free will be a boon to implementing this functionality elsewhere.
This commit is contained in:
Jose Diaz-Gonzalez
2020-11-21 19:36:13 -05:00
parent a19efc1cfa
commit e6e5ede436
3 changed files with 139 additions and 140 deletions

135
plugins/common/parallel.go Normal file
View File

@@ -0,0 +1,135 @@
package common
import (
"fmt"
"runtime"
"sync"
)
// ParallelCommand is a type that declares functions
// the ps plugins can execute in parallel
type ParallelCommand func(string) error
// ParallelCommandRun contains all the arguments
// necessary for a parallel command run
type ParallelCommandRun struct {
Name string
}
// ParallelCommandResult is the result of a parallel
// command run
type ParallelCommandResult struct {
Name string
Error error
}
// RunCommandAgainstAllApps runs a given ParallelCommand against all apps
func RunCommandAgainstAllApps(command ParallelCommand, commandName string, parallelCount int) error {
runInSerial := false
if parallelCount < -1 {
return fmt.Errorf("Invalid value %d for --parallel flag", parallelCount)
}
if parallelCount == -1 {
cpuCount := runtime.NumCPU()
LogWarn(fmt.Sprintf("Setting --parallel=%d value to CPU count of %d", parallelCount, cpuCount))
parallelCount = cpuCount
}
if parallelCount == 0 || parallelCount == 1 {
LogWarn(fmt.Sprintf("Running %s in serial mode", commandName))
runInSerial = true
}
if runInSerial {
return runCommandAgainstAllAppsSerially(command, commandName)
}
return runCommandAgainstAllAppsInParallel(command, commandName, parallelCount)
}
func runCommandAgainstAllAppsInParallel(command ParallelCommand, commandName string, parallelCount int) error {
apps, err := DokkuApps()
if err != nil {
LogWarn(err.Error())
return nil
}
jobs := make(chan string, parallelCount)
results := make(chan ParallelCommandResult, len(apps))
go allocateJobs(apps, jobs)
done := make(chan error)
go aggregateResults(results, done)
createParallelWorkerPool(jobs, results, command, parallelCount)
err = <-done
return err
}
func runCommandAgainstAllAppsSerially(command ParallelCommand, commandName string) error {
apps, err := DokkuApps()
if err != nil {
LogWarn(err.Error())
return nil
}
errorCount := 0
for _, appName := range apps {
LogInfo1(fmt.Sprintf("Running %s against app %s", commandName, appName))
if err = command(appName); err != nil {
errorCount++
}
}
if errorCount > 0 {
return fmt.Errorf("%s command returned %d errors", commandName, errorCount)
}
return nil
}
func allocateJobs(input []string, jobs chan string) {
for _, job := range input {
jobs <- job
}
close(jobs)
}
func aggregateResults(results chan ParallelCommandResult, done chan error) {
var parallelError error
errorCount := 0
for result := range results {
if result.Error != nil {
LogWarn(fmt.Sprintf("Error running command against %s", result.Name))
errorCount++
}
}
if errorCount > 0 {
parallelError = fmt.Errorf("Encountered %d errors during parallel run", errorCount)
}
done <- parallelError
}
func createParallelWorker(jobs chan string, results chan ParallelCommandResult, command ParallelCommand, wg *sync.WaitGroup, workerID int) {
for job := range jobs {
LogInfo1(fmt.Sprintf("Running command against %s", job))
output := ParallelCommandResult{
Name: job,
Error: command(job),
}
results <- output
}
wg.Done()
}
func createParallelWorkerPool(jobs chan string, results chan ParallelCommandResult, command ParallelCommand, numberOfWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numberOfWorkers; i++ {
wg.Add(1)
go createParallelWorker(jobs, results, command, &wg, i)
}
wg.Wait()
close(results)
}

View File

@@ -2,11 +2,8 @@ package ps
import (
"fmt"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/dokku/dokku/plugins/common"
)
@@ -15,23 +12,6 @@ import (
// and defaults to -1 (false)
const RunInSerial = 0
// ParallelCommand is a type that declares functions
// the ps plugins can execute in parallel
type ParallelCommand func(string) error
// ParallelCommandRun contains all the arguments
// necessary for a parallel command run
type ParallelCommandRun struct {
AppName string
}
// ParallelCommandResult is the result of a parallel
// command run
type ParallelCommandResult struct {
AppName string
Error error
}
var (
// DefaultProperties is a map of all valid ps properties with corresponding default property values
DefaultProperties = map[string]string{
@@ -39,122 +19,6 @@ var (
}
)
// RunCommandAgainstAllApps runs a given ParallelCommand against all apps
func RunCommandAgainstAllApps(command ParallelCommand, commandName string, parallelCount int) error {
runInSerial := false
if parallelCount == -1 {
cpuCount := runtime.NumCPU()
common.LogWarn(fmt.Sprintf("Setting --parallel=%d value to CPU count of %d", parallelCount, cpuCount))
parallelCount = cpuCount
}
if parallelCount == 0 || parallelCount == 1 {
common.LogWarn(fmt.Sprintf("Running %s in serial mode", commandName))
runInSerial = true
}
if runInSerial {
return RunCommandAgainstAllAppsSerially(command, commandName)
}
return RunCommandAgainstAllAppsInParallel(command, commandName, parallelCount)
}
// RunCommandAgainstAllAppsInParallel runs a given
// ParallelCommand against all apps in parallel
func RunCommandAgainstAllAppsInParallel(command ParallelCommand, commandName string, parallelCount int) error {
apps, err := common.DokkuApps()
if err != nil {
common.LogWarn(err.Error())
return nil
}
jobs := make(chan string, parallelCount)
results := make(chan ParallelCommandResult, len(apps))
allocate := func() {
for _, appName := range apps {
jobs <- appName
}
close(jobs)
}
result := func(done chan error) {
var parallelError error
errorCount := 0
for result := range results {
if result.Error != nil {
common.LogWarn(fmt.Sprintf("Error running %s against app %s", commandName, result.AppName))
errorCount++
}
}
if errorCount > 0 {
parallelError = fmt.Errorf("Encountered %d errors during parallel run", errorCount)
}
done <- parallelError
}
worker := func(wg *sync.WaitGroup, workerID int) {
for appName := range jobs {
common.LogInfo1(fmt.Sprintf("Running %s against app %s", commandName, appName))
output := ParallelCommandResult{
AppName: appName,
Error: command(appName),
}
results <- output
}
wg.Done()
}
createParallelWorkerPool := func(numberOfWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numberOfWorkers; i++ {
wg.Add(1)
go worker(&wg, i)
}
wg.Wait()
close(results)
}
startTime := time.Now()
go allocate()
done := make(chan error)
go result(done)
createParallelWorkerPool(parallelCount)
err = <-done
endTime := time.Now()
diff := endTime.Sub(startTime)
common.LogInfo2Quiet(fmt.Sprintf("Total time taken: %.2f seconds", diff.Seconds()))
return err
}
// RunCommandAgainstAllAppsSerially runs a given
// ParallelCommand against all apps in serial
func RunCommandAgainstAllAppsSerially(command ParallelCommand, commandName string) error {
apps, err := common.DokkuApps()
if err != nil {
common.LogWarn(err.Error())
return nil
}
errorCount := 0
for _, appName := range apps {
common.LogInfo1(fmt.Sprintf("Running %s against app %s", commandName, appName))
if err = command(appName); err != nil {
errorCount++
}
}
if errorCount > 0 {
return fmt.Errorf("%s command returned %d errors", commandName, errorCount)
}
return nil
}
// Rebuild rebuilds app from base image
func Rebuild(appName string) error {
return common.PlugnTrigger("receive-app", []string{appName}...)

View File

@@ -27,7 +27,7 @@ func CommandInspect(appName string) error {
// CommandRebuild rebuilds an app from source
func CommandRebuild(appName string, allApps bool, parallelCount int) error {
if allApps {
return RunCommandAgainstAllApps(Rebuild, "rebuild", parallelCount)
return common.RunCommandAgainstAllApps(Rebuild, "rebuild", parallelCount)
}
if appName == "" {
@@ -67,7 +67,7 @@ func CommandReport(appName string, infoFlag string) error {
// CommandRestart restarts an app
func CommandRestart(appName string, allApps bool, parallelCount int) error {
if allApps {
return RunCommandAgainstAllApps(Restart, "restart", parallelCount)
return common.RunCommandAgainstAllApps(Restart, "restart", parallelCount)
}
if appName == "" {
@@ -150,7 +150,7 @@ func CommandSet(appName string, property string, value string) error {
// CommandStart starts an app
func CommandStart(appName string, allApps bool, parallelCount int) error {
if allApps {
return RunCommandAgainstAllApps(Start, "start", parallelCount)
return common.RunCommandAgainstAllApps(Start, "start", parallelCount)
}
if appName == "" {
@@ -167,7 +167,7 @@ func CommandStart(appName string, allApps bool, parallelCount int) error {
// CommandStop stops an app
func CommandStop(appName string, allApps bool, parallelCount int) error {
if allApps {
return RunCommandAgainstAllApps(Stop, "stop", parallelCount)
return common.RunCommandAgainstAllApps(Stop, "stop", parallelCount)
}
if appName == "" {