diff options
Diffstat (limited to 'cmd/podman/shared')
-rw-r--r-- | cmd/podman/shared/container.go | 4 | ||||
-rw-r--r-- | cmd/podman/shared/funcs.go | 41 | ||||
-rw-r--r-- | cmd/podman/shared/funcs_test.go | 87 | ||||
-rw-r--r-- | cmd/podman/shared/parallel.go | 91 |
4 files changed, 211 insertions, 12 deletions
diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 4af737e0a..b847314a4 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -226,10 +226,10 @@ func NewBatchContainer(ctr *libpod.Container, opts PsOptions) (PsContainerOutput return pso, nil } -type pFunc func() (PsContainerOutput, error) +type batchFunc func() (PsContainerOutput, error) type workerInput struct { - parallelFunc pFunc + parallelFunc batchFunc opts PsOptions cid string job int diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go index 485944f29..a92e0d547 100644 --- a/cmd/podman/shared/funcs.go +++ b/cmd/podman/shared/funcs.go @@ -3,11 +3,39 @@ package shared import ( "fmt" "os" + "path/filepath" "strings" ) +func substituteCommand(cmd string) (string, error) { + // If cmd is an absolute or relative path, check if the file exists. + // Throw an error if it doesn't exist. + if strings.Contains(cmd, "/") || strings.HasPrefix(cmd, ".") { + res, err := filepath.Abs(cmd) + if err != nil { + return "", err + } + if _, err := os.Stat(res); !os.IsNotExist(err) { + return res, nil + } else if err != nil { + return "", err + } + } + + // Replace cmd with "/proc/self/exe" if "podman" or "docker" is being + // used. Otherwise, leave the command unchanged. + switch cmd { + case "podman": + fallthrough + case "docker": + return "/proc/self/exe", nil + default: + return cmd, nil + } +} + // GenerateCommand takes a label (string) and converts it to an executable command -func GenerateCommand(command, imageName, name string) []string { +func GenerateCommand(command, imageName, name string) ([]string, error) { var ( newCommand []string ) @@ -15,8 +43,13 @@ func GenerateCommand(command, imageName, name string) []string { name = imageName } cmd := strings.Split(command, " ") - // Replace the first element of cmd with "/proc/self/exe" - newCommand = append(newCommand, "/proc/self/exe") + + prog, err := substituteCommand(cmd[0]) + if err != nil { + return nil, err + } + newCommand = append(newCommand, prog) + for _, arg := range cmd[1:] { var newArg string switch arg { @@ -37,7 +70,7 @@ func GenerateCommand(command, imageName, name string) []string { } newCommand = append(newCommand, newArg) } - return newCommand + return newCommand, nil } // GenerateRunEnvironment merges the current environment variables with optional diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go index 612be480b..596df84e8 100644 --- a/cmd/podman/shared/funcs_test.go +++ b/cmd/podman/shared/funcs_test.go @@ -1,6 +1,10 @@ package shared import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" "strings" "testing" @@ -16,35 +20,106 @@ var ( func TestGenerateCommand(t *testing.T) { inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install" - newCommand := GenerateCommand(inputCommand, "foo", "bar") + newCommand, err := GenerateCommand(inputCommand, "foo", "bar") + assert.Nil(t, err) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } +func TestGenerateCommandCheckSubstitution(t *testing.T) { + type subsTest struct { + input string + expected string + shouldFail bool + } + + absTmpFile, err := ioutil.TempFile("", "podmanRunlabelTestAbsolutePath") + assert.Nil(t, err, "error creating tempfile") + defer os.Remove(absTmpFile.Name()) + + relTmpFile, err := ioutil.TempFile("./", "podmanRunlabelTestRelativePath") + assert.Nil(t, err, "error creating tempfile") + defer os.Remove(relTmpFile.Name()) + relTmpCmd, err := filepath.Abs(relTmpFile.Name()) + assert.Nil(t, err, "error getting absolute path for relative tmpfile") + + // this has a (low) potential of race conditions but no other way + removedTmpFile, err := ioutil.TempFile("", "podmanRunlabelTestRemove") + assert.Nil(t, err, "error creating tempfile") + os.Remove(removedTmpFile.Name()) + + absTmpCmd := fmt.Sprintf("%s --flag1 --flag2 --args=foo", absTmpFile.Name()) + tests := []subsTest{ + { + input: "docker run -it alpine:latest", + expected: "/proc/self/exe run -it alpine:latest", + shouldFail: false, + }, + { + input: "podman run -it alpine:latest", + expected: "/proc/self/exe run -it alpine:latest", + shouldFail: false, + }, + { + input: absTmpCmd, + expected: absTmpCmd, + shouldFail: false, + }, + { + input: "./" + relTmpFile.Name(), + expected: relTmpCmd, + shouldFail: false, + }, + { + input: "ls -la", + expected: "ls -la", + shouldFail: false, + }, + { + input: removedTmpFile.Name(), + expected: "", + shouldFail: true, + }, + } + + for _, test := range tests { + newCommand, err := GenerateCommand(test.input, "foo", "bar") + if test.shouldFail { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + assert.Equal(t, test.expected, strings.Join(newCommand, " ")) + } +} + func TestGenerateCommandPath(t *testing.T) { - inputCommand := "/usr/bin/docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" + inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install" - newCommand := GenerateCommand(inputCommand, "foo", "bar") + newCommand, _ := GenerateCommand(inputCommand, "foo", "bar") assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } func TestGenerateCommandNoSetName(t *testing.T) { inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" correctCommand := "/proc/self/exe run -it --name foo -e NAME=foo -e IMAGE=foo foo echo install" - newCommand := GenerateCommand(inputCommand, "foo", "") + newCommand, err := GenerateCommand(inputCommand, "foo", "") + assert.Nil(t, err) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } func TestGenerateCommandNoName(t *testing.T) { inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install" correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install" - newCommand := GenerateCommand(inputCommand, "foo", "") + newCommand, err := GenerateCommand(inputCommand, "foo", "") + assert.Nil(t, err) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } func TestGenerateCommandAlreadyPodman(t *testing.T) { inputCommand := "podman run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install" correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install" - newCommand := GenerateCommand(inputCommand, "foo", "bar") + newCommand, err := GenerateCommand(inputCommand, "foo", "bar") + assert.Nil(t, err) assert.Equal(t, correctCommand, strings.Join(newCommand, " ")) } diff --git a/cmd/podman/shared/parallel.go b/cmd/podman/shared/parallel.go new file mode 100644 index 000000000..03eba2f0b --- /dev/null +++ b/cmd/podman/shared/parallel.go @@ -0,0 +1,91 @@ +package shared + +import ( + "runtime" + "sync" +) + +type pFunc func() error + +// ParallelWorkerInput is a struct used to pass in a slice of parallel funcs to be +// performed on a container ID +type ParallelWorkerInput struct { + ContainerID string + ParallelFunc pFunc +} + +type containerError struct { + ContainerID string + Err error +} + +// ParallelWorker is a "threaded" worker that takes jobs from the channel "queue" +func ParallelWorker(wg *sync.WaitGroup, jobs <-chan ParallelWorkerInput, results chan<- containerError) { + for j := range jobs { + err := j.ParallelFunc() + results <- containerError{ContainerID: j.ContainerID, Err: err} + wg.Done() + } +} + +// ParallelExecuteWorkerPool takes container jobs and performs them in parallel. The worker +// int determines how many workers/threads should be premade. +func ParallelExecuteWorkerPool(workers int, functions []ParallelWorkerInput) map[string]error { + var ( + wg sync.WaitGroup + ) + + resultChan := make(chan containerError, len(functions)) + results := make(map[string]error) + paraJobs := make(chan ParallelWorkerInput, len(functions)) + + // If we have more workers than functions, match up the number of workers and functions + if workers > len(functions) { + workers = len(functions) + } + + // Create the workers + for w := 1; w <= workers; w++ { + go ParallelWorker(&wg, paraJobs, resultChan) + } + + // Add jobs to the workers + for _, j := range functions { + j := j + wg.Add(1) + paraJobs <- j + } + + close(paraJobs) + wg.Wait() + + close(resultChan) + for ctrError := range resultChan { + results[ctrError.ContainerID] = ctrError.Err + } + + return results +} + +// Parallelize provides the maximum number of parallel workers (int) as calculated by a basic +// heuristic. This can be overriden by the --max-workers primary switch to podman. +func Parallelize(job string) int { + numCpus := runtime.NumCPU() + switch job { + case "stop": + if numCpus <= 2 { + return 4 + } else { + return numCpus * 3 + } + case "rm": + if numCpus <= 3 { + return numCpus * 3 + } else { + return numCpus * 4 + } + case "ps": + return 8 + } + return 3 +} |