8000
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 27 additions & 90 deletions integration/container/update_linux_test.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
package container // import "github.com/docker/docker/integration/container"

import (
"bytes"
"context"
"io/ioutil"
"strconv"
"strings"
"testing"
"time"

"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/request"
"github.com/docker/docker/pkg/stdcopy"
"github.com/gotestyourself/gotestyourself/poll"
"github.com/gotestyourself/gotestyourself/skip"
"github.com/stretchr/testify/assert"
Expand All @@ -39,26 +33,37 @@ func TestUpdateMemory(t *testing.T) {

poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))

const (
setMemory int64 = 314572800
setMemorySwap = 524288000
)

_, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{
Resources: containertypes.Resources{
Memory: 314572800,
MemorySwap: 524288000,
Memory: setMemory,
MemorySwap: setMemorySwap,
},
})
require.NoError(t, err)

inspect, err := client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.HostConfig.Memory, int64(314572800))
assert.Equal(t, inspect.HostConfig.MemorySwap, int64(524288000))
assert.Equal(t, setMemory, inspect.HostConfig.Memory)
assert.Equal(t, setMemorySwap, inspect.HostConfig.MemorySwap)

body, err := getContainerSysFSValue(ctx, client, cID, "/sys/fs/cgroup/memory/memory.limit_in_bytes")
res, err := container.Exec(ctx, client, cID,
[]string{"cat", "/sys/fs/cgroup/memory/memory.limit_in_bytes"})
@boaz0 boaz0 Feb 20, 2018
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why the binary here is cat while in TestUpdateCPUQuota it is explicitly given as /bin/cat?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular reason; we expect that /bin entry is present in $PATH, so no need to use an absolute path. I general, I'd prefer relying on $PATH and not giving absolute paths -- it's a bit more portable that way.

require.NoError(t, err)
assert.Equal(t, strings.TrimSpace(body), "314572800")
require.Empty(t, res.Stderr())
require.Equal(t, 0, res.ExitCode)
assert.Equal(t, strconv.FormatInt(setMemory, 10), strings.TrimSpace(res.Stdout()))

body, err = getContainerSysFSValue(ctx, client, cID, "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes")
res, err = container.Exec(ctx, client, cID,
[]string{"cat", "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"})
require.NoError(t, err)
assert.Equal(t, strings.TrimSpace(body), "524288000")
require.Empty(t, res.Stderr())
require.Equal(t, 0, res.ExitCode)
assert.Equal(t, strconv.FormatInt(setMemorySwap, 10), strings.TrimSpace(res.Stdout()))
}

func TestUpdateCPUQUota(t *testing.T) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the function name should be TestUpdateCPUQuota and not TestUpdateCPUQUota

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that looks like a typo, but that code wasn't touched in this PR; perhaps you can open a separate PR for that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; I'll fix.

Expand Down Expand Up @@ -88,83 +93,15 @@ func TestUpdateCPUQUota(t *testing.T) {
}

inspect, err := client.ContainerInspect(ctx, cID)
if err != nil {
t.Fatal(err)
}

if inspect.HostConfig.CPUQuota != test.update {
t.Fatalf("quota not updated in the API, expected %d, got: %d", test.update, inspect.HostConfig.CPUQuota)
}

execCreate, err := client.ContainerExecCreate(ctx, cID, types.ExecConfig{
Cmd: []string{"/bin/cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"},
AttachStdout: true,
AttachStderr: true,
})
if err != nil {
t.Fatal(err)
}

attach, err := client.ContainerExecAttach(ctx, execCreate.ID, types.ExecStartCheck{})
if err != nil {
t.Fatal(err)
}

if err := client.ContainerExecStart(ctx, execCreate.ID, types.ExecStartCheck{}); err != nil {
t.Fatal(err)
}
require.NoError(t, err)
assert.Equal(t, test.update, inspect.HostConfig.CPUQuota)

buf := bytes.NewBuffer(nil)
ready := make(chan error)

go func() {
_, err := stdcopy.StdCopy(buf, buf, attach.Reader)
ready <- err
}()

select {
case <-time.After(60 * time.Second):
t.Fatal("timeout waiting for exec to complete")
case err := <-ready:
if err != nil {
t.Fatal(err)
}
}
res, err := container.Exec(ctx, client, cID,
[]string{"/bin/cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"})
require.NoError(t, err)
require.Empty(t, res.Stderr())
require.Equal(t, 0, res.ExitCode)

actual := strings.TrimSpace(buf.String())
if actual != strconv.Itoa(int(test.update)) {
t.Fatalf("expected cgroup value %d, got: %s", test.update, actual)
}
assert.Equal(t, strconv.FormatInt(test.update, 10), strings.TrimSpace(res.Stdout()))
}

}

func getContainerSysFSValue(ctx context.Context, client client.APIClient, cID string, path string) (string, error) {
var b bytes.Buffer

ex, err := client.ContainerExecCreate(ctx, cID,
types.ExecConfig{
AttachStdout: true,
Cmd: strslice.StrSlice([]string{"cat", path}),
},
)
if err != nil {
return "", err
}

resp, err := client.ContainerExecAttach(ctx, ex.ID,
types.ExecStartCheck{
Detach: false,
Tty: false,
},
)
if err != nil {
return "", err
}

defer resp.Close()

b.Reset()
_, err = stdcopy.StdCopy(&b, ioutil.Discard, resp.Reader)
return b.String(), err
}
86 changes: 86 additions & 0 deletions integration/internal/container/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package container

import (
"bytes"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"golang.org/x/net/context"
)

// ExecResult represents a result returned from Exec()
type ExecResult struct {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually; perhaps this type doesn’t have to be exported

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it and it the (way too strict IMO) linter gives:

15:55:50 integration/internal/container/exec.go:37:80:warning: exported func Exec returns unexported type container.execResult, which can be annoying to use (golint)

so I had to revert it back to they way it was

ExitCode int
outBuffer *bytes.Buffer
errBuffer *bytes.Buffer
}

// Stdout returns stdout output of a command run by Exec()
func (res *ExecResult) Stdout() string {
return res.outBuffer.String()
}

// Stderr returns stderr output of a command run by Exec()
func (res *ExecResult) Stderr() string {
return res.errBuffer.String()
}

// Combined returns combined stdout and stderr output of a command run by Exec()
func (res *ExecResult) Combined() string {
return res.outBuffer.String() + res.errBuffer.String()
}

// Exec executes a command inside a container, returning the result
// containing stdout, stderr, and exit code. Note:
// - this is a synchronous operation;
// - cmd stdin is closed.
func Exec(ctx context.Context, cli client.APIClient, id string, cmd []string) (ExecResult, error) {
// prepare exec
execConfig := types.ExecConfig{
AttachStdout: true,
AttachStderr: true,
Cmd: cmd,
}
cresp, err := cli.ContainerExecCreate(ctx, id, execConfig)
if err != nil {
return ExecResult{}, err
}
execID := cresp.ID

// run it, with stdout/stderr attached
aresp, err := cli.ContainerExecAttach(ctx, execID, types.ExecStartCheck{})
if err != nil {
return ExecResult{}, err
}
defer aresp.Close()

// read the output
var outBuf, errBuf bytes.Buffer
outputDone := make(chan error)

go func() {
// StdCopy demultiplexes the stream into two buffers
_, err = stdcopy.StdCopy(&outBuf, &errBuf, aresp.Reader)
outputDone <- err
}()

select {
case err := <-outputDone:
if err != nil {
return ExecResult{}, err
}
break

case <-ctx.Done():
return ExecResult{}, ctx.Err()
}

// get the exit code
iresp, err := cli.ContainerExecInspect(ctx, execID)
if err != nil {
return ExecResult{}, err
}

return ExecResult{ExitCode: iresp.ExitCode, outBuffer: &outBuf, errBuffer: &errBuf}, nil
}
0