update vendor

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-09-25 12:27:46 -04:00
parent 19a32db84d
commit 94d1cfbfbf
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
10501 changed files with 2307943 additions and 29279 deletions

View file

@ -0,0 +1,130 @@
package build
import (
"context"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/docker/api/types"
dclient "github.com/docker/docker/client"
"github.com/docker/docker/internal/test/fakecontext"
"github.com/docker/docker/internal/test/request"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/filesync"
"golang.org/x/sync/errgroup"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestBuildWithSession(t *testing.T) {
skip.If(t, !testEnv.DaemonInfo.ExperimentalBuild)
client := testEnv.APIClient()
dockerfile := `
FROM busybox
COPY file /
RUN cat /file
`
fctx := fakecontext.New(t, "",
fakecontext.WithFile("file", "some content"),
)
defer fctx.Close()
out := testBuildWithSession(t, client, client.DaemonHost(), fctx.Dir, dockerfile)
assert.Check(t, is.Contains(out, "some content"))
fctx.Add("second", "contentcontent")
dockerfile += `
COPY second /
RUN cat /second
`
out = testBuildWithSession(t, client, client.DaemonHost(), fctx.Dir, dockerfile)
assert.Check(t, is.Equal(strings.Count(out, "Using cache"), 2))
assert.Check(t, is.Contains(out, "contentcontent"))
du, err := client.DiskUsage(context.TODO())
assert.Check(t, err)
assert.Check(t, du.BuilderSize > 10)
out = testBuildWithSession(t, client, client.DaemonHost(), fctx.Dir, dockerfile)
assert.Check(t, is.Equal(strings.Count(out, "Using cache"), 4))
du2, err := client.DiskUsage(context.TODO())
assert.Check(t, err)
assert.Check(t, is.Equal(du.BuilderSize, du2.BuilderSize))
// rebuild with regular tar, confirm cache still applies
fctx.Add("Dockerfile", dockerfile)
// FIXME(vdemeester) use sock here
res, body, err := request.Do(
"/build",
request.Host(client.DaemonHost()),
request.Method(http.MethodPost),
request.RawContent(fctx.AsTarReader(t)),
request.ContentType("application/x-tar"))
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(http.StatusOK, res.StatusCode))
outBytes, err := request.ReadBody(body)
assert.NilError(t, err)
assert.Check(t, is.Contains(string(outBytes), "Successfully built"))
assert.Check(t, is.Equal(strings.Count(string(outBytes), "Using cache"), 4))
_, err = client.BuildCachePrune(context.TODO(), types.BuildCachePruneOptions{All: true})
assert.Check(t, err)
du, err = client.DiskUsage(context.TODO())
assert.Check(t, err)
assert.Check(t, is.Equal(du.BuilderSize, int64(0)))
}
func testBuildWithSession(t *testing.T, client dclient.APIClient, daemonHost string, dir, dockerfile string) (outStr string) {
ctx := context.Background()
sess, err := session.NewSession(ctx, "foo1", "foo")
assert.Check(t, err)
fsProvider := filesync.NewFSSyncProvider([]filesync.SyncedDir{
{Dir: dir},
})
sess.Allow(fsProvider)
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error {
return sess.Run(ctx, client.DialSession)
})
g.Go(func() error {
// FIXME use sock here
res, body, err := request.Do(
"/build?remote=client-session&session="+sess.ID(),
request.Host(daemonHost),
request.Method(http.MethodPost),
request.With(func(req *http.Request) error {
req.Body = ioutil.NopCloser(strings.NewReader(dockerfile))
return nil
}),
)
if err != nil {
return err
}
assert.Check(t, is.DeepEqual(res.StatusCode, http.StatusOK))
out, err := request.ReadBody(body)
assert.NilError(t, err)
assert.Check(t, is.Contains(string(out), "Successfully built"))
sess.Close()
outStr = string(out)
return nil
})
err = g.Wait()
assert.Check(t, err)
return
}

View file

@ -0,0 +1,103 @@
package build
import (
"bytes"
"context"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/fakecontext"
"github.com/docker/docker/pkg/stdcopy"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestBuildSquashParent(t *testing.T) {
skip.If(t, !testEnv.DaemonInfo.ExperimentalBuild)
client := testEnv.APIClient()
dockerfile := `
FROM busybox
RUN echo hello > /hello
RUN echo world >> /hello
RUN echo hello > /remove_me
ENV HELLO world
RUN rm /remove_me
`
// build and get the ID that we can use later for history comparison
ctx := context.Background()
source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
defer source.Close()
name := "test"
resp, err := client.ImageBuild(ctx,
source.AsTarReader(t),
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
Tags: []string{name},
})
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
inspect, _, err := client.ImageInspectWithRaw(ctx, name)
assert.NilError(t, err)
origID := inspect.ID
// build with squash
resp, err = client.ImageBuild(ctx,
source.AsTarReader(t),
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
Squash: true,
Tags: []string{name},
})
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
cid := container.Run(t, ctx, client,
container.WithImage(name),
container.WithCmd("/bin/sh", "-c", "cat /hello"),
)
reader, err := client.ContainerLogs(ctx, cid, types.ContainerLogsOptions{
ShowStdout: true,
})
assert.NilError(t, err)
actualStdout := new(bytes.Buffer)
actualStderr := ioutil.Discard
_, err = stdcopy.StdCopy(actualStdout, actualStderr, reader)
assert.NilError(t, err)
assert.Check(t, is.Equal(strings.TrimSpace(actualStdout.String()), "hello\nworld"))
container.Run(t, ctx, client,
container.WithImage(name),
container.WithCmd("/bin/sh", "-c", "[ ! -f /remove_me ]"),
)
container.Run(t, ctx, client,
container.WithImage(name),
container.WithCmd("/bin/sh", "-c", `[ "$(echo $HELLO)" == "world" ]`),
)
origHistory, err := client.ImageHistory(ctx, origID)
assert.NilError(t, err)
testHistory, err := client.ImageHistory(ctx, name)
assert.NilError(t, err)
inspect, _, err = client.ImageInspectWithRaw(ctx, name)
assert.NilError(t, err)
assert.Check(t, is.Len(testHistory, len(origHistory)+1))
assert.Check(t, is.Len(inspect.RootFS.Layers, 2))
}

View file

@ -0,0 +1,460 @@
package build // import "github.com/docker/docker/integration/build"
import (
"archive/tar"
"bytes"
"context"
"encoding/json"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/internal/test/fakecontext"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/pkg/jsonmessage"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestBuildWithRemoveAndForceRemove(t *testing.T) {
defer setupTest(t)()
t.Parallel()
cases := []struct {
name string
dockerfile string
numberOfIntermediateContainers int
rm bool
forceRm bool
}{
{
name: "successful build with no removal",
dockerfile: `FROM busybox
RUN exit 0
RUN exit 0`,
numberOfIntermediateContainers: 2,
rm: false,
forceRm: false,
},
{
name: "successful build with remove",
dockerfile: `FROM busybox
RUN exit 0
RUN exit 0`,
numberOfIntermediateContainers: 0,
rm: true,
forceRm: false,
},
{
name: "successful build with remove and force remove",
dockerfile: `FROM busybox
RUN exit 0
RUN exit 0`,
numberOfIntermediateContainers: 0,
rm: true,
forceRm: true,
},
{
name: "failed build with no removal",
dockerfile: `FROM busybox
RUN exit 0
RUN exit 1`,
numberOfIntermediateContainers: 2,
rm: false,
forceRm: false,
},
{
name: "failed build with remove",
dockerfile: `FROM busybox
RUN exit 0
RUN exit 1`,
numberOfIntermediateContainers: 1,
rm: true,
forceRm: false,
},
{
name: "failed build with remove and force remove",
dockerfile: `FROM busybox
RUN exit 0
RUN exit 1`,
numberOfIntermediateContainers: 0,
rm: true,
forceRm: true,
},
}
client := request.NewAPIClient(t)
ctx := context.Background()
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
t.Parallel()
dockerfile := []byte(c.dockerfile)
buff := bytes.NewBuffer(nil)
tw := tar.NewWriter(buff)
assert.NilError(t, tw.WriteHeader(&tar.Header{
Name: "Dockerfile",
Size: int64(len(dockerfile)),
}))
_, err := tw.Write(dockerfile)
assert.NilError(t, err)
assert.NilError(t, tw.Close())
resp, err := client.ImageBuild(ctx, buff, types.ImageBuildOptions{Remove: c.rm, ForceRemove: c.forceRm, NoCache: true})
assert.NilError(t, err)
defer resp.Body.Close()
filter, err := buildContainerIdsFilter(resp.Body)
assert.NilError(t, err)
remainingContainers, err := client.ContainerList(ctx, types.ContainerListOptions{Filters: filter, All: true})
assert.NilError(t, err)
assert.Equal(t, c.numberOfIntermediateContainers, len(remainingContainers), "Expected %v remaining intermediate containers, got %v", c.numberOfIntermediateContainers, len(remainingContainers))
})
}
}
func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
const intermediateContainerPrefix = " ---> Running in "
filter := filters.NewArgs()
dec := json.NewDecoder(buildOutput)
for {
m := jsonmessage.JSONMessage{}
err := dec.Decode(&m)
if err == io.EOF {
return filter, nil
}
if err != nil {
return filter, err
}
if ix := strings.Index(m.Stream, intermediateContainerPrefix); ix != -1 {
filter.Add("id", strings.TrimSpace(m.Stream[ix+len(intermediateContainerPrefix):]))
}
}
}
func TestBuildMultiStageParentConfig(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
dockerfile := `
FROM busybox AS stage0
ENV WHO=parent
WORKDIR /foo
FROM stage0
ENV WHO=sibling1
WORKDIR sub1
FROM stage0
WORKDIR sub2
`
ctx := context.Background()
source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
defer source.Close()
apiclient := testEnv.APIClient()
resp, err := apiclient.ImageBuild(ctx,
source.AsTarReader(t),
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
Tags: []string{"build1"},
})
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
image, _, err := apiclient.ImageInspectWithRaw(ctx, "build1")
assert.NilError(t, err)
assert.Check(t, is.Equal("/foo/sub2", image.Config.WorkingDir))
assert.Check(t, is.Contains(image.Config.Env, "WHO=parent"))
}
// Test cases in #36996
func TestBuildLabelWithTargets(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "test added after 1.38")
bldName := "build-a"
testLabels := map[string]string{
"foo": "bar",
"dead": "beef",
}
dockerfile := `
FROM busybox AS target-a
CMD ["/dev"]
LABEL label-a=inline-a
FROM busybox AS target-b
CMD ["/dist"]
LABEL label-b=inline-b
`
ctx := context.Background()
source := fakecontext.New(t, "", fakecontext.WithDockerfile(dockerfile))
defer source.Close()
apiclient := testEnv.APIClient()
// For `target-a` build
resp, err := apiclient.ImageBuild(ctx,
source.AsTarReader(t),
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
Tags: []string{bldName},
Labels: testLabels,
Target: "target-a",
})
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
image, _, err := apiclient.ImageInspectWithRaw(ctx, bldName)
assert.NilError(t, err)
testLabels["label-a"] = "inline-a"
for k, v := range testLabels {
x, ok := image.Config.Labels[k]
assert.Assert(t, ok)
assert.Assert(t, x == v)
}
// For `target-b` build
bldName = "build-b"
delete(testLabels, "label-a")
resp, err = apiclient.ImageBuild(ctx,
source.AsTarReader(t),
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
Tags: []string{bldName},
Labels: testLabels,
Target: "target-b",
})
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
image, _, err = apiclient.ImageInspectWithRaw(ctx, bldName)
assert.NilError(t, err)
testLabels["label-b"] = "inline-b"
for k, v := range testLabels {
x, ok := image.Config.Labels[k]
assert.Assert(t, ok)
assert.Assert(t, x == v)
}
}
func TestBuildWithEmptyLayers(t *testing.T) {
dockerfile := `
FROM busybox
COPY 1/ /target/
COPY 2/ /target/
COPY 3/ /target/
`
ctx := context.Background()
source := fakecontext.New(t, "",
fakecontext.WithDockerfile(dockerfile),
fakecontext.WithFile("1/a", "asdf"),
fakecontext.WithFile("2/a", "asdf"),
fakecontext.WithFile("3/a", "asdf"))
defer source.Close()
apiclient := testEnv.APIClient()
resp, err := apiclient.ImageBuild(ctx,
source.AsTarReader(t),
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
})
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
}
// TestBuildMultiStageOnBuild checks that ONBUILD commands are applied to
// multiple subsequent stages
// #35652
func TestBuildMultiStageOnBuild(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.33"), "broken in earlier versions")
defer setupTest(t)()
// test both metadata and layer based commands as they may be implemented differently
dockerfile := `FROM busybox AS stage1
ONBUILD RUN echo 'foo' >somefile
ONBUILD ENV bar=baz
FROM stage1
RUN cat somefile # fails if ONBUILD RUN fails
FROM stage1
RUN cat somefile`
ctx := context.Background()
source := fakecontext.New(t, "",
fakecontext.WithDockerfile(dockerfile))
defer source.Close()
apiclient := testEnv.APIClient()
resp, err := apiclient.ImageBuild(ctx,
source.AsTarReader(t),
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
})
out := bytes.NewBuffer(nil)
assert.NilError(t, err)
_, err = io.Copy(out, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
assert.Check(t, is.Contains(out.String(), "Successfully built"))
imageIDs, err := getImageIDsFromBuild(out.Bytes())
assert.NilError(t, err)
assert.Check(t, is.Equal(3, len(imageIDs)))
image, _, err := apiclient.ImageInspectWithRaw(context.Background(), imageIDs[2])
assert.NilError(t, err)
assert.Check(t, is.Contains(image.Config.Env, "bar=baz"))
}
// #35403 #36122
func TestBuildUncleanTarFilenames(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
ctx := context.TODO()
defer setupTest(t)()
dockerfile := `FROM scratch
COPY foo /
FROM scratch
COPY bar /`
buf := bytes.NewBuffer(nil)
w := tar.NewWriter(buf)
writeTarRecord(t, w, "Dockerfile", dockerfile)
writeTarRecord(t, w, "../foo", "foocontents0")
writeTarRecord(t, w, "/bar", "barcontents0")
err := w.Close()
assert.NilError(t, err)
apiclient := testEnv.APIClient()
resp, err := apiclient.ImageBuild(ctx,
buf,
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
})
out := bytes.NewBuffer(nil)
assert.NilError(t, err)
_, err = io.Copy(out, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
// repeat with changed data should not cause cache hits
buf = bytes.NewBuffer(nil)
w = tar.NewWriter(buf)
writeTarRecord(t, w, "Dockerfile", dockerfile)
writeTarRecord(t, w, "../foo", "foocontents1")
writeTarRecord(t, w, "/bar", "barcontents1")
err = w.Close()
assert.NilError(t, err)
resp, err = apiclient.ImageBuild(ctx,
buf,
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
})
out = bytes.NewBuffer(nil)
assert.NilError(t, err)
_, err = io.Copy(out, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
assert.Assert(t, !strings.Contains(out.String(), "Using cache"))
}
// docker/for-linux#135
// #35641
func TestBuildMultiStageLayerLeak(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.37"), "broken in earlier versions")
ctx := context.TODO()
defer setupTest(t)()
// all commands need to match until COPY
dockerfile := `FROM busybox
WORKDIR /foo
COPY foo .
FROM busybox
WORKDIR /foo
COPY bar .
RUN [ -f bar ]
RUN [ ! -f foo ]
`
source := fakecontext.New(t, "",
fakecontext.WithFile("foo", "0"),
fakecontext.WithFile("bar", "1"),
fakecontext.WithDockerfile(dockerfile))
defer source.Close()
apiclient := testEnv.APIClient()
resp, err := apiclient.ImageBuild(ctx,
source.AsTarReader(t),
types.ImageBuildOptions{
Remove: true,
ForceRemove: true,
})
out := bytes.NewBuffer(nil)
assert.NilError(t, err)
_, err = io.Copy(out, resp.Body)
resp.Body.Close()
assert.NilError(t, err)
assert.Check(t, is.Contains(out.String(), "Successfully built"))
}
func writeTarRecord(t *testing.T, w *tar.Writer, fn, contents string) {
err := w.WriteHeader(&tar.Header{
Name: fn,
Mode: 0600,
Size: int64(len(contents)),
Typeflag: '0',
})
assert.NilError(t, err)
_, err = w.Write([]byte(contents))
assert.NilError(t, err)
}
type buildLine struct {
Stream string
Aux struct {
ID string
}
}
func getImageIDsFromBuild(output []byte) ([]string, error) {
var ids []string
for _, line := range bytes.Split(output, []byte("\n")) {
if len(line) == 0 {
continue
}
entry := buildLine{}
if err := json.Unmarshal(line, &entry); err != nil {
return nil, err
}
if entry.Aux.ID != "" {
ids = append(ids, entry.Aux.ID)
}
}
return ids, nil
}

View file

@ -0,0 +1,33 @@
package build // import "github.com/docker/docker/integration/build"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,433 @@
package config // import "github.com/docker/docker/integration/config"
import (
"bytes"
"context"
"encoding/json"
"sort"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/swarm"
"github.com/docker/docker/pkg/stdcopy"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestConfigList(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
// This test case is ported from the original TestConfigsEmptyList
configs, err := client.ConfigList(ctx, types.ConfigListOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(len(configs), 0))
testName0 := "test0-" + t.Name()
testName1 := "test1-" + t.Name()
testNames := []string{testName0, testName1}
sort.Strings(testNames)
// create config test0
createConfig(ctx, t, client, testName0, []byte("TESTINGDATA0"), map[string]string{"type": "test"})
config1ID := createConfig(ctx, t, client, testName1, []byte("TESTINGDATA1"), map[string]string{"type": "production"})
// test by `config ls`
entries, err := client.ConfigList(ctx, types.ConfigListOptions{})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(configNamesFromList(entries), testNames))
testCases := []struct {
filters filters.Args
expected []string
}{
// test filter by name `config ls --filter name=xxx`
{
filters: filters.NewArgs(filters.Arg("name", testName0)),
expected: []string{testName0},
},
// test filter by id `config ls --filter id=xxx`
{
filters: filters.NewArgs(filters.Arg("id", config1ID)),
expected: []string{testName1},
},
// test filter by label `config ls --filter label=xxx`
{
filters: filters.NewArgs(filters.Arg("label", "type")),
expected: testNames,
},
{
filters: filters.NewArgs(filters.Arg("label", "type=test")),
expected: []string{testName0},
},
{
filters: filters.NewArgs(filters.Arg("label", "type=production")),
expected: []string{testName1},
},
}
for _, tc := range testCases {
entries, err = client.ConfigList(ctx, types.ConfigListOptions{
Filters: tc.filters,
})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(configNamesFromList(entries), tc.expected))
}
}
func createConfig(ctx context.Context, t *testing.T, client client.APIClient, name string, data []byte, labels map[string]string) string {
config, err := client.ConfigCreate(ctx, swarmtypes.ConfigSpec{
Annotations: swarmtypes.Annotations{
Name: name,
Labels: labels,
},
Data: data,
})
assert.NilError(t, err)
assert.Check(t, config.ID != "")
return config.ID
}
func TestConfigsCreateAndDelete(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
testName := "test_config-" + t.Name()
// This test case is ported from the original TestConfigsCreate
configID := createConfig(ctx, t, client, testName, []byte("TESTINGDATA"), nil)
insp, _, err := client.ConfigInspectWithRaw(ctx, configID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Name, testName))
// This test case is ported from the original TestConfigsDelete
err = client.ConfigRemove(ctx, configID)
assert.NilError(t, err)
insp, _, err = client.ConfigInspectWithRaw(ctx, configID)
assert.Check(t, is.ErrorContains(err, "No such config"))
}
func TestConfigsUpdate(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
testName := "test_config-" + t.Name()
// This test case is ported from the original TestConfigsCreate
configID := createConfig(ctx, t, client, testName, []byte("TESTINGDATA"), nil)
insp, _, err := client.ConfigInspectWithRaw(ctx, configID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.ID, configID))
// test UpdateConfig with full ID
insp.Spec.Labels = map[string]string{"test": "test1"}
err = client.ConfigUpdate(ctx, configID, insp.Version, insp.Spec)
assert.NilError(t, err)
insp, _, err = client.ConfigInspectWithRaw(ctx, configID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test1"))
// test UpdateConfig with full name
insp.Spec.Labels = map[string]string{"test": "test2"}
err = client.ConfigUpdate(ctx, testName, insp.Version, insp.Spec)
assert.NilError(t, err)
insp, _, err = client.ConfigInspectWithRaw(ctx, configID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test2"))
// test UpdateConfig with prefix ID
insp.Spec.Labels = map[string]string{"test": "test3"}
err = client.ConfigUpdate(ctx, configID[:1], insp.Version, insp.Spec)
assert.NilError(t, err)
insp, _, err = client.ConfigInspectWithRaw(ctx, configID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test3"))
// test UpdateConfig in updating Data which is not supported in daemon
// this test will produce an error in func UpdateConfig
insp.Spec.Data = []byte("TESTINGDATA2")
err = client.ConfigUpdate(ctx, configID, insp.Version, insp.Spec)
assert.Check(t, is.ErrorContains(err, "only updates to Labels are allowed"))
}
func TestTemplatedConfig(t *testing.T) {
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
referencedSecretName := "referencedsecret-" + t.Name()
referencedSecretSpec := swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{
Name: referencedSecretName,
},
Data: []byte("this is a secret"),
}
referencedSecret, err := client.SecretCreate(ctx, referencedSecretSpec)
assert.Check(t, err)
referencedConfigName := "referencedconfig-" + t.Name()
referencedConfigSpec := swarmtypes.ConfigSpec{
Annotations: swarmtypes.Annotations{
Name: referencedConfigName,
},
Data: []byte("this is a config"),
}
referencedConfig, err := client.ConfigCreate(ctx, referencedConfigSpec)
assert.Check(t, err)
templatedConfigName := "templated_config-" + t.Name()
configSpec := swarmtypes.ConfigSpec{
Annotations: swarmtypes.Annotations{
Name: templatedConfigName,
},
Templating: &swarmtypes.Driver{
Name: "golang",
},
Data: []byte("SERVICE_NAME={{.Service.Name}}\n" +
"{{secret \"referencedsecrettarget\"}}\n" +
"{{config \"referencedconfigtarget\"}}\n"),
}
templatedConfig, err := client.ConfigCreate(ctx, configSpec)
assert.Check(t, err)
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithConfig(
&swarmtypes.ConfigReference{
File: &swarmtypes.ConfigReferenceFileTarget{
Name: "/" + templatedConfigName,
UID: "0",
GID: "0",
Mode: 0600,
},
ConfigID: templatedConfig.ID,
ConfigName: templatedConfigName,
},
),
swarm.ServiceWithConfig(
&swarmtypes.ConfigReference{
File: &swarmtypes.ConfigReferenceFileTarget{
Name: "referencedconfigtarget",
UID: "0",
GID: "0",
Mode: 0600,
},
ConfigID: referencedConfig.ID,
ConfigName: referencedConfigName,
},
),
swarm.ServiceWithSecret(
&swarmtypes.SecretReference{
File: &swarmtypes.SecretReferenceFileTarget{
Name: "referencedsecrettarget",
UID: "0",
GID: "0",
Mode: 0600,
},
SecretID: referencedSecret.ID,
SecretName: referencedSecretName,
},
),
swarm.ServiceWithName("svc"),
)
var tasks []swarmtypes.Task
waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
tasks = swarm.GetRunningTasks(t, d, serviceID)
return len(tasks) > 0
})
task := tasks[0]
waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
if task.NodeID == "" || (task.Status.ContainerStatus == nil || task.Status.ContainerStatus.ContainerID == "") {
task, _, _ = client.TaskInspectWithRaw(context.Background(), task.ID)
}
return task.NodeID != "" && task.Status.ContainerStatus != nil && task.Status.ContainerStatus.ContainerID != ""
})
attach := swarm.ExecTask(t, d, task, types.ExecConfig{
Cmd: []string{"/bin/cat", "/" + templatedConfigName},
AttachStdout: true,
AttachStderr: true,
})
expect := "SERVICE_NAME=svc\n" +
"this is a secret\n" +
"this is a config\n"
assertAttachedStream(t, attach, expect)
attach = swarm.ExecTask(t, d, task, types.ExecConfig{
Cmd: []string{"mount"},
AttachStdout: true,
AttachStderr: true,
})
assertAttachedStream(t, attach, "tmpfs on /"+templatedConfigName+" type tmpfs")
}
func assertAttachedStream(t *testing.T, attach types.HijackedResponse, expect string) {
buf := bytes.NewBuffer(nil)
_, err := stdcopy.StdCopy(buf, buf, attach.Reader)
assert.NilError(t, err)
assert.Check(t, is.Contains(buf.String(), expect))
}
func waitAndAssert(t *testing.T, timeout time.Duration, f func(*testing.T) bool) {
t.Helper()
after := time.After(timeout)
for {
select {
case <-after:
t.Fatalf("timed out waiting for condition")
default:
}
if f(t) {
return
}
time.Sleep(100 * time.Millisecond)
}
}
func TestConfigInspect(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
testName := t.Name()
configID := createConfig(ctx, t, client, testName, []byte("TESTINGDATA"), nil)
insp, body, err := client.ConfigInspectWithRaw(ctx, configID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Name, testName))
var config swarmtypes.Config
err = json.Unmarshal(body, &config)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(config, insp))
}
func TestConfigCreateWithLabels(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
labels := map[string]string{
"key1": "value1",
"key2": "value2",
}
testName := t.Name()
configID := createConfig(ctx, t, client, testName, []byte("TESTINGDATA"), labels)
insp, _, err := client.ConfigInspectWithRaw(ctx, configID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Name, testName))
assert.Check(t, is.Equal(2, len(insp.Spec.Labels)))
assert.Check(t, is.Equal("value1", insp.Spec.Labels["key1"]))
assert.Check(t, is.Equal("value2", insp.Spec.Labels["key2"]))
}
// Test case for 28884
func TestConfigCreateResolve(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
configName := "test_config_" + t.Name()
configID := createConfig(ctx, t, client, configName, []byte("foo"), nil)
fakeName := configID
fakeID := createConfig(ctx, t, client, fakeName, []byte("fake foo"), nil)
entries, err := client.ConfigList(ctx, types.ConfigListOptions{})
assert.NilError(t, err)
assert.Assert(t, is.Contains(configNamesFromList(entries), configName))
assert.Assert(t, is.Contains(configNamesFromList(entries), fakeName))
err = client.ConfigRemove(ctx, configID)
assert.NilError(t, err)
// Fake one will remain
entries, err = client.ConfigList(ctx, types.ConfigListOptions{})
assert.NilError(t, err)
assert.Assert(t, is.DeepEqual(configNamesFromList(entries), []string{fakeName}))
// Remove based on name prefix of the fake one
// (which is the same as the ID of foo one) should not work
// as search is only done based on:
// - Full ID
// - Full Name
// - Partial ID (prefix)
err = client.ConfigRemove(ctx, configID[:5])
assert.Assert(t, nil != err)
entries, err = client.ConfigList(ctx, types.ConfigListOptions{})
assert.NilError(t, err)
assert.Assert(t, is.DeepEqual(configNamesFromList(entries), []string{fakeName}))
// Remove based on ID prefix of the fake one should succeed
err = client.ConfigRemove(ctx, fakeID[:5])
assert.NilError(t, err)
entries, err = client.ConfigList(ctx, types.ConfigListOptions{})
assert.NilError(t, err)
assert.Assert(t, is.Equal(0, len(entries)))
}
func configNamesFromList(entries []swarmtypes.Config) []string {
var values []string
for _, entry := range entries {
values = append(values, entry.Spec.Name)
}
sort.Strings(values)
return values
}

View file

@ -0,0 +1,33 @@
package config // import "github.com/docker/docker/integration/config"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,65 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"fmt"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestCopyFromContainerPathDoesNotExist(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
apiclient := testEnv.APIClient()
cid := container.Create(t, ctx, apiclient)
_, _, err := apiclient.CopyFromContainer(ctx, cid, "/dne")
assert.Check(t, client.IsErrNotFound(err))
expected := fmt.Sprintf("No such container:path: %s:%s", cid, "/dne")
assert.Check(t, is.ErrorContains(err, expected))
}
func TestCopyFromContainerPathIsNotDir(t *testing.T) {
defer setupTest(t)()
skip.If(t, testEnv.OSType == "windows")
ctx := context.Background()
apiclient := testEnv.APIClient()
cid := container.Create(t, ctx, apiclient)
_, _, err := apiclient.CopyFromContainer(ctx, cid, "/etc/passwd/")
assert.Assert(t, is.ErrorContains(err, "not a directory"))
}
func TestCopyToContainerPathDoesNotExist(t *testing.T) {
defer setupTest(t)()
skip.If(t, testEnv.OSType == "windows")
ctx := context.Background()
apiclient := testEnv.APIClient()
cid := container.Create(t, ctx, apiclient)
err := apiclient.CopyToContainer(ctx, cid, "/dne", nil, types.CopyToContainerOptions{})
assert.Check(t, client.IsErrNotFound(err))
expected := fmt.Sprintf("No such container:path: %s:%s", cid, "/dne")
assert.Check(t, is.ErrorContains(err, expected))
}
func TestCopyToContainerPathIsNotDir(t *testing.T) {
defer setupTest(t)()
skip.If(t, testEnv.OSType == "windows")
ctx := context.Background()
apiclient := testEnv.APIClient()
cid := container.Create(t, ctx, apiclient)
err := apiclient.CopyToContainer(ctx, cid, "/etc/passwd/", nil, types.CopyToContainerOptions{})
assert.Assert(t, is.ErrorContains(err, "not a directory"))
}

View file

@ -0,0 +1,303 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"encoding/json"
"fmt"
"strconv"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
ctr "github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/oci"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
testCases := []struct {
doc string
image string
expectedError string
}{
{
doc: "image and tag",
image: "test456:v1",
expectedError: "No such image: test456:v1",
},
{
doc: "image no tag",
image: "test456",
expectedError: "No such image: test456",
},
{
doc: "digest",
image: "sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa",
expectedError: "No such image: sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
t.Parallel()
_, err := client.ContainerCreate(context.Background(),
&container.Config{Image: tc.image},
&container.HostConfig{},
&network.NetworkingConfig{},
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
})
}
}
func TestCreateWithInvalidEnv(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
testCases := []struct {
env string
expectedError string
}{
{
env: "",
expectedError: "invalid environment variable:",
},
{
env: "=",
expectedError: "invalid environment variable: =",
},
{
env: "=foo",
expectedError: "invalid environment variable: =foo",
},
}
for index, tc := range testCases {
tc := tc
t.Run(strconv.Itoa(index), func(t *testing.T) {
t.Parallel()
_, err := client.ContainerCreate(context.Background(),
&container.Config{
Image: "busybox",
Env: []string{tc.env},
},
&container.HostConfig{},
&network.NetworkingConfig{},
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
})
}
}
// Test case for #30166 (target was not validated)
func TestCreateTmpfsMountsTarget(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
client := request.NewAPIClient(t)
testCases := []struct {
target string
expectedError string
}{
{
target: ".",
expectedError: "mount path must be absolute",
},
{
target: "foo",
expectedError: "mount path must be absolute",
},
{
target: "/",
expectedError: "destination can't be '/'",
},
{
target: "//",
expectedError: "destination can't be '/'",
},
}
for _, tc := range testCases {
_, err := client.ContainerCreate(context.Background(),
&container.Config{
Image: "busybox",
},
&container.HostConfig{
Tmpfs: map[string]string{tc.target: ""},
},
&network.NetworkingConfig{},
"",
)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
}
}
func TestCreateWithCustomMaskedPaths(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
testCases := []struct {
maskedPaths []string
expected []string
}{
{
maskedPaths: []string{},
expected: []string{},
},
{
maskedPaths: nil,
expected: oci.DefaultSpec().Linux.MaskedPaths,
},
{
maskedPaths: []string{"/proc/kcore", "/proc/keys"},
expected: []string{"/proc/kcore", "/proc/keys"},
},
}
checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
_, b, err := client.ContainerInspectWithRaw(ctx, name, false)
assert.NilError(t, err)
var inspectJSON map[string]interface{}
err = json.Unmarshal(b, &inspectJSON)
assert.NilError(t, err)
cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
assert.Check(t, is.Equal(true, ok), name)
maskedPaths, ok := cfg["MaskedPaths"].([]interface{})
assert.Check(t, is.Equal(true, ok), name)
mps := []string{}
for _, mp := range maskedPaths {
mps = append(mps, mp.(string))
}
assert.DeepEqual(t, expected, mps)
}
for i, tc := range testCases {
name := fmt.Sprintf("create-masked-paths-%d", i)
config := container.Config{
Image: "busybox",
Cmd: []string{"true"},
}
hc := container.HostConfig{}
if tc.maskedPaths != nil {
hc.MaskedPaths = tc.maskedPaths
}
// Create the container.
c, err := client.ContainerCreate(context.Background(),
&config,
&hc,
&network.NetworkingConfig{},
name,
)
assert.NilError(t, err)
checkInspect(t, ctx, name, tc.expected)
// Start the container.
err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
assert.NilError(t, err)
poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
checkInspect(t, ctx, name, tc.expected)
}
}
func TestCreateWithCustomReadonlyPaths(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
testCases := []struct {
doc string
readonlyPaths []string
expected []string
}{
{
readonlyPaths: []string{},
expected: []string{},
},
{
readonlyPaths: nil,
expected: oci.DefaultSpec().Linux.ReadonlyPaths,
},
{
readonlyPaths: []string{"/proc/asound", "/proc/bus"},
expected: []string{"/proc/asound", "/proc/bus"},
},
}
checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
_, b, err := client.ContainerInspectWithRaw(ctx, name, false)
assert.NilError(t, err)
var inspectJSON map[string]interface{}
err = json.Unmarshal(b, &inspectJSON)
assert.NilError(t, err)
cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
assert.Check(t, is.Equal(true, ok), name)
readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{})
assert.Check(t, is.Equal(true, ok), name)
rops := []string{}
for _, rop := range readonlyPaths {
rops = append(rops, rop.(string))
}
assert.DeepEqual(t, expected, rops)
}
for i, tc := range testCases {
name := fmt.Sprintf("create-readonly-paths-%d", i)
config := container.Config{
Image: "busybox",
Cmd: []string{"true"},
}
hc := container.HostConfig{}
if tc.readonlyPaths != nil {
hc.ReadonlyPaths = tc.readonlyPaths
}
// Create the container.
c, err := client.ContainerCreate(context.Background(),
&config,
&hc,
&network.NetworkingConfig{},
name,
)
assert.NilError(t, err)
checkInspect(t, ctx, name, tc.expected)
// Start the container.
err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
assert.NilError(t, err)
poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
checkInspect(t, ctx, name, tc.expected)
}
}

View file

@ -0,0 +1,78 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"fmt"
"io/ioutil"
"strconv"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/daemon"
"golang.org/x/sys/unix"
"gotest.tools/assert"
"gotest.tools/skip"
)
// This is a regression test for #36145
// It ensures that a container can be started when the daemon was improperly
// shutdown when the daemon is brought back up.
//
// The regression is due to improper error handling preventing a container from
// being restored and as such have the resources cleaned up.
//
// To test this, we need to kill dockerd, then kill both the containerd-shim and
// the container process, then start dockerd back up and attempt to start the
// container again.
func TestContainerStartOnDaemonRestart(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run")
t.Parallel()
d := daemon.New(t)
d.StartWithBusybox(t, "--iptables=false")
defer d.Stop(t)
client, err := d.NewClient()
assert.Check(t, err, "error creating client")
ctx := context.Background()
cID := container.Create(t, ctx, client)
defer client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true})
err = client.ContainerStart(ctx, cID, types.ContainerStartOptions{})
assert.Check(t, err, "error starting test container")
inspect, err := client.ContainerInspect(ctx, cID)
assert.Check(t, err, "error getting inspect data")
ppid := getContainerdShimPid(t, inspect)
err = d.Kill()
assert.Check(t, err, "failed to kill test daemon")
err = unix.Kill(inspect.State.Pid, unix.SIGKILL)
assert.Check(t, err, "failed to kill container process")
err = unix.Kill(ppid, unix.SIGKILL)
assert.Check(t, err, "failed to kill containerd-shim")
d.Start(t, "--iptables=false")
err = client.ContainerStart(ctx, cID, types.ContainerStartOptions{})
assert.Check(t, err, "failed to start test container")
}
func getContainerdShimPid(t *testing.T, c types.ContainerJSON) int {
statB, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", c.State.Pid))
assert.Check(t, err, "error looking up containerd-shim pid")
// ppid is the 4th entry in `/proc/pid/stat`
ppid, err := strconv.Atoi(strings.Fields(string(statB))[3])
assert.Check(t, err, "error converting ppid field to int")
assert.Check(t, ppid != 1, "got unexpected ppid")
return ppid
}

View file

@ -0,0 +1,42 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/pkg/archive"
"gotest.tools/assert"
"gotest.tools/poll"
)
func TestDiff(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, container.WithCmd("sh", "-c", `mkdir /foo; echo xyzzy > /foo/bar`))
// Wait for it to exit as cannot diff a running container on Windows, and
// it will take a few seconds to exit. Also there's no way in Windows to
// differentiate between an Add or a Modify, and all files are under
// a "Files/" prefix.
expected := []containertypes.ContainerChangeResponseItem{
{Kind: archive.ChangeAdd, Path: "/foo"},
{Kind: archive.ChangeAdd, Path: "/foo/bar"},
}
if testEnv.OSType == "windows" {
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(60*time.Second))
expected = []containertypes.ContainerChangeResponseItem{
{Kind: archive.ChangeModify, Path: "Files/foo"},
{Kind: archive.ChangeModify, Path: "Files/foo/bar"},
}
}
items, err := client.ContainerDiff(ctx, cID)
assert.NilError(t, err)
assert.DeepEqual(t, expected, items)
}

View file

@ -0,0 +1,50 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"io/ioutil"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestExec(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.35"), "broken in earlier versions")
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
cID := container.Run(t, ctx, client, container.WithTty(true), container.WithWorkingDir("/root"))
id, err := client.ContainerExecCreate(ctx, cID,
types.ExecConfig{
WorkingDir: "/tmp",
Env: strslice.StrSlice([]string{"FOO=BAR"}),
AttachStdout: true,
Cmd: strslice.StrSlice([]string{"sh", "-c", "env"}),
},
)
assert.NilError(t, err)
resp, err := client.ContainerExecAttach(ctx, id.ID,
types.ExecStartCheck{
Detach: false,
Tty: false,
},
)
assert.NilError(t, err)
defer resp.Close()
r, err := ioutil.ReadAll(resp.Reader)
assert.NilError(t, err)
out := string(r)
assert.NilError(t, err)
assert.Assert(t, is.Contains(out, "PWD=/tmp"), "exec command not running in expected /tmp working directory")
assert.Assert(t, is.Contains(out, "FOO=BAR"), "exec command not running with expected environment variable FOO")
}

View file

@ -0,0 +1,78 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/pkg/jsonmessage"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
// export an image and try to import it into a new one
func TestExportContainerAndImportImage(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, container.WithCmd("true"))
poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
reference := "repo/testexp:v1"
exportResp, err := client.ContainerExport(ctx, cID)
assert.NilError(t, err)
importResp, err := client.ImageImport(ctx, types.ImageImportSource{
Source: exportResp,
SourceName: "-",
}, reference, types.ImageImportOptions{})
assert.NilError(t, err)
// If the import is successfully, then the message output should contain
// the image ID and match with the output from `docker images`.
dec := json.NewDecoder(importResp)
var jm jsonmessage.JSONMessage
err = dec.Decode(&jm)
assert.NilError(t, err)
images, err := client.ImageList(ctx, types.ImageListOptions{
Filters: filters.NewArgs(filters.Arg("reference", reference)),
})
assert.NilError(t, err)
assert.Check(t, is.Equal(jm.Status, images[0].ID))
}
// TestExportContainerAfterDaemonRestart checks that a container
// created before start of the currently running dockerd
// can be exported (as reported in #36561). To satisfy this
// condition, daemon restart is needed after container creation.
func TestExportContainerAfterDaemonRestart(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsRemoteDaemon())
d := daemon.New(t)
client, err := d.NewClient()
assert.NilError(t, err)
d.StartWithBusybox(t)
defer d.Stop(t)
ctx := context.Background()
ctrID := container.Create(t, ctx, client)
d.Restart(t)
_, err = client.ContainerExport(ctx, ctrID)
assert.NilError(t, err)
}

View file

@ -0,0 +1,47 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"testing"
"time"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/poll"
)
// TestHealthCheckWorkdir verifies that health-checks inherit the containers'
// working-dir.
func TestHealthCheckWorkdir(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
cID := container.Run(t, ctx, client, container.WithTty(true), container.WithWorkingDir("/foo"), func(c *container.TestContainerConfig) {
c.Config.Healthcheck = &containertypes.HealthConfig{
Test: []string{"CMD-SHELL", "if [ \"$PWD\" = \"/foo\" ]; then exit 0; else exit 1; fi;"},
Interval: 50 * time.Millisecond,
Retries: 3,
}
})
poll.WaitOn(t, pollForHealthStatus(ctx, client, cID, types.Healthy), poll.WithDelay(100*time.Millisecond))
}
func pollForHealthStatus(ctx context.Context, client client.APIClient, containerID string, healthStatus string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
inspect, err := client.ContainerInspect(ctx, containerID)
switch {
case err != nil:
return poll.Error(err)
case inspect.State.Health.Status == healthStatus:
return poll.Success()
default:
return poll.Continue("waiting for container to become %s", healthStatus)
}
}
}

View file

@ -0,0 +1,48 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestInspectCpusetInConfigPre120(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || !testEnv.DaemonInfo.CPUSet)
defer setupTest(t)()
client := request.NewAPIClient(t, client.WithVersion("1.19"))
ctx := context.Background()
name := "cpusetinconfig-pre120-" + t.Name()
// Create container with up to-date-API
container.Run(t, ctx, request.NewAPIClient(t), container.WithName(name),
container.WithCmd("true"),
func(c *container.TestContainerConfig) {
c.HostConfig.Resources.CpusetCpus = "0"
},
)
poll.WaitOn(t, container.IsInState(ctx, client, name, "exited"), poll.WithDelay(100*time.Millisecond))
_, body, err := client.ContainerInspectWithRaw(ctx, name, false)
assert.NilError(t, err)
var inspectJSON map[string]interface{}
err = json.Unmarshal(body, &inspectJSON)
assert.NilError(t, err, "unable to unmarshal body for version 1.19: %s", err)
config, ok := inspectJSON["Config"]
assert.Check(t, is.Equal(true, ok), "Unable to find 'Config'")
cfg := config.(map[string]interface{})
_, ok = cfg["Cpuset"]
assert.Check(t, is.Equal(true, ok), "API version 1.19 expected to include Cpuset in 'Config'")
}

View file

@ -0,0 +1,181 @@
package container // import "github.com/docker/docker/integration/container"
import (
"bufio"
"context"
"os"
"regexp"
"strings"
"testing"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
// testIpcCheckDevExists checks whether a given mount (identified by its
// major:minor pair from /proc/self/mountinfo) exists on the host system.
//
// The format of /proc/self/mountinfo is like:
//
// 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw
// ^^^^\
// - this is the minor:major we look for
func testIpcCheckDevExists(mm string) (bool, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return false, err
}
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
fields := strings.Fields(s.Text())
if len(fields) < 7 {
continue
}
if fields[2] == mm {
return true, nil
}
}
return false, s.Err()
}
// testIpcNonePrivateShareable is a helper function to test "none",
// "private" and "shareable" modes.
func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool, mustBeShared bool) {
defer setupTest(t)()
cfg := containertypes.Config{
Image: "busybox",
Cmd: []string{"top"},
}
hostCfg := containertypes.HostConfig{
IpcMode: containertypes.IpcMode(mode),
}
client := request.NewAPIClient(t)
ctx := context.Background()
resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
assert.NilError(t, err)
assert.Check(t, is.Equal(len(resp.Warnings), 0))
err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
assert.NilError(t, err)
// get major:minor pair for /dev/shm from container's /proc/self/mountinfo
cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
result, err := container.Exec(ctx, client, resp.ID, []string{"sh", "-c", cmd})
assert.NilError(t, err)
mm := result.Combined()
if !mustBeMounted {
assert.Check(t, is.Equal(mm, ""))
// no more checks to perform
return
}
assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm)))
shared, err := testIpcCheckDevExists(mm)
assert.NilError(t, err)
t.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared)
assert.Check(t, is.Equal(shared, mustBeShared))
}
// TestIpcModeNone checks the container "none" IPC mode
// (--ipc none) works as expected. It makes sure there is no
// /dev/shm mount inside the container.
func TestIpcModeNone(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
testIpcNonePrivateShareable(t, "none", false, false)
}
// TestAPIIpcModePrivate checks the container private IPC mode
// (--ipc private) works as expected. It gets the minor:major pair
// of /dev/shm mount from the container, and makes sure there is no
// such pair on the host.
func TestIpcModePrivate(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
testIpcNonePrivateShareable(t, "private", true, false)
}
// TestAPIIpcModeShareable checks the container shareable IPC mode
// (--ipc shareable) works as expected. It gets the minor:major pair
// of /dev/shm mount from the container, and makes sure such pair
// also exists on the host.
func TestIpcModeShareable(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
testIpcNonePrivateShareable(t, "shareable", true, true)
}
// testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios
func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
t.Helper()
defer setupTest(t)()
cfg := containertypes.Config{
Image: "busybox",
Cmd: []string{"top"},
}
hostCfg := containertypes.HostConfig{
IpcMode: containertypes.IpcMode(donorMode),
}
ctx := context.Background()
client := request.NewAPIClient(t)
// create and start the "donor" container
resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
assert.NilError(t, err)
assert.Check(t, is.Equal(len(resp.Warnings), 0))
name1 := resp.ID
err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{})
assert.NilError(t, err)
// create and start the second container
hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
assert.NilError(t, err)
assert.Check(t, is.Equal(len(resp.Warnings), 0))
name2 := resp.ID
err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{})
if !mustWork {
// start should fail with a specific error
assert.Check(t, is.ErrorContains(err, "non-shareable IPC"))
// no more checks to perform here
return
}
// start should succeed
assert.NilError(t, err)
// check that IPC is shared
// 1. create a file in the first container
_, err = container.Exec(ctx, client, name1, []string{"sh", "-c", "printf covfefe > /dev/shm/bar"})
assert.NilError(t, err)
// 2. check it's the same file in the second one
result, err := container.Exec(ctx, client, name2, []string{"cat", "/dev/shm/bar"})
assert.NilError(t, err)
out := result.Combined()
assert.Check(t, is.Equal(true, regexp.MustCompile("^covfefe$").MatchString(out)))
}
// TestAPIIpcModeShareableAndPrivate checks that
// 1) a container created with --ipc container:ID can use IPC of another shareable container.
// 2) a container created with --ipc container:ID can NOT use IPC of another private container.
func TestAPIIpcModeShareableAndContainer(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
testIpcContainer(t, "shareable", true)
testIpcContainer(t, "private", false)
}

View file

@ -0,0 +1,183 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestKillContainerInvalidSignal(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
id := container.Run(t, ctx, client)
err := client.ContainerKill(ctx, id, "0")
assert.Error(t, err, "Error response from daemon: Invalid signal: 0")
poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
err = client.ContainerKill(ctx, id, "SIG42")
assert.Error(t, err, "Error response from daemon: Invalid signal: SIG42")
poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
}
func TestKillContainer(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
testCases := []struct {
doc string
signal string
status string
}{
{
doc: "no signal",
signal: "",
status: "exited",
},
{
doc: "non killing signal",
signal: "SIGWINCH",
status: "running",
},
{
doc: "killing signal",
signal: "SIGTERM",
status: "exited",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
ctx := context.Background()
id := container.Run(t, ctx, client)
err := client.ContainerKill(ctx, id, tc.signal)
assert.NilError(t, err)
poll.WaitOn(t, container.IsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond))
})
}
}
func TestKillWithStopSignalAndRestartPolicies(t *testing.T) {
skip.If(t, testEnv.OSType != "linux", "Windows only supports 1.25 or later")
defer setupTest(t)()
client := request.NewAPIClient(t)
testCases := []struct {
doc string
stopsignal string
status string
}{
{
doc: "same-signal-disables-restart-policy",
stopsignal: "TERM",
status: "exited",
},
{
doc: "different-signal-keep-restart-policy",
stopsignal: "CONT",
status: "running",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
ctx := context.Background()
id := container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
c.Config.StopSignal = tc.stopsignal
c.HostConfig.RestartPolicy = containertypes.RestartPolicy{
Name: "always",
}
})
err := client.ContainerKill(ctx, id, "TERM")
assert.NilError(t, err)
poll.WaitOn(t, container.IsInState(ctx, client, id, tc.status), poll.WithDelay(100*time.Millisecond))
})
}
}
func TestKillStoppedContainer(t *testing.T) {
skip.If(t, testEnv.OSType != "linux") // Windows only supports 1.25 or later
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
id := container.Create(t, ctx, client)
err := client.ContainerKill(ctx, id, "SIGKILL")
assert.Assert(t, is.ErrorContains(err, ""))
assert.Assert(t, is.Contains(err.Error(), "is not running"))
}
func TestKillStoppedContainerAPIPre120(t *testing.T) {
skip.If(t, testEnv.OSType != "linux") // Windows only supports 1.25 or later
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t, client.WithVersion("1.19"))
id := container.Create(t, ctx, client)
err := client.ContainerKill(ctx, id, "SIGKILL")
assert.NilError(t, err)
}
func TestKillDifferentUserContainer(t *testing.T) {
// TODO Windows: Windows does not yet support -u (Feb 2016).
skip.If(t, testEnv.OSType != "linux", "User containers (container.Config.User) are not yet supported on %q platform", testEnv.OSType)
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t, client.WithVersion("1.19"))
id := container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
c.Config.User = "daemon"
})
poll.WaitOn(t, container.IsInState(ctx, client, id, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerKill(ctx, id, "SIGKILL")
assert.NilError(t, err)
poll.WaitOn(t, container.IsInState(ctx, client, id, "exited"), poll.WithDelay(100*time.Millisecond))
}
func TestInspectOomKilledTrue(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || !testEnv.DaemonInfo.MemoryLimit || !testEnv.DaemonInfo.SwapLimit)
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
cID := container.Run(t, ctx, client, container.WithCmd("sh", "-c", "x=a; while true; do x=$x$x$x$x; done"), func(c *container.TestContainerConfig) {
c.HostConfig.Resources.Memory = 32 * 1024 * 1024
})
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal(true, inspect.State.OOMKilled))
}
func TestInspectOomKilledFalse(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || !testEnv.DaemonInfo.MemoryLimit || !testEnv.DaemonInfo.SwapLimit)
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
cID := container.Run(t, ctx, client, container.WithCmd("sh", "-c", "echo hello world"))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal(false, inspect.State.OOMKilled))
}

View file

@ -0,0 +1,57 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"io/ioutil"
"os"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestLinksEtcHostsContentMatch(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
hosts, err := ioutil.ReadFile("/etc/hosts")
skip.If(t, os.IsNotExist(err))
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, container.WithNetworkMode("host"))
res, err := container.Exec(ctx, client, cID, []string{"cat", "/etc/hosts"})
assert.NilError(t, err)
assert.Assert(t, is.Len(res.Stderr(), 0))
assert.Equal(t, 0, res.ExitCode)
assert.Check(t, is.Equal(string(hosts), res.Stdout()))
}
func TestLinksContainerNames(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
containerA := "first_" + t.Name()
containerB := "second_" + t.Name()
container.Run(t, ctx, client, container.WithName(containerA))
container.Run(t, ctx, client, container.WithName(containerB), container.WithLinks(containerA+":"+containerA))
f := filters.NewArgs(filters.Arg("name", containerA))
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Filters: f,
})
assert.NilError(t, err)
assert.Check(t, is.Equal(1, len(containers)))
assert.Check(t, is.DeepEqual([]string{"/" + containerA, "/" + containerB + "/" + containerA}, containers[0].Names))
}

View file

@ -0,0 +1,35 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"io/ioutil"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/pkg/stdcopy"
"gotest.tools/assert"
"gotest.tools/skip"
)
// Regression test for #35370
// Makes sure that when following we don't get an EOF error when there are no logs
func TestLogsFollowTailEmpty(t *testing.T) {
// FIXME(vdemeester) fails on a e2e run on linux...
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
id := container.Run(t, ctx, client, container.WithCmd("sleep", "100000"))
logs, err := client.ContainerLogs(ctx, id, types.ContainerLogsOptions{ShowStdout: true, Tail: "2"})
if logs != nil {
defer logs.Close()
}
assert.Check(t, err)
_, err = stdcopy.StdCopy(ioutil.Discard, ioutil.Discard, logs)
assert.Check(t, err)
}

View file

@ -0,0 +1,33 @@
package container // import "github.com/docker/docker/integration/container"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,208 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"fmt"
"path/filepath"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/pkg/system"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/fs"
"gotest.tools/skip"
)
func TestContainerNetworkMountsNoChown(t *testing.T) {
// chown only applies to Linux bind mounted volumes; must be same host to verify
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
defer setupTest(t)()
ctx := context.Background()
tmpDir := fs.NewDir(t, "network-file-mounts", fs.WithMode(0755), fs.WithFile("nwfile", "network file bind mount", fs.WithMode(0644)))
defer tmpDir.Remove()
tmpNWFileMount := tmpDir.Join("nwfile")
config := container.Config{
Image: "busybox",
}
hostConfig := container.HostConfig{
Mounts: []mount.Mount{
{
Type: "bind",
Source: tmpNWFileMount,
Target: "/etc/resolv.conf",
},
{
Type: "bind",
Source: tmpNWFileMount,
Target: "/etc/hostname",
},
{
Type: "bind",
Source: tmpNWFileMount,
Target: "/etc/hosts",
},
},
}
cli, err := client.NewEnvClient()
assert.NilError(t, err)
defer cli.Close()
ctrCreate, err := cli.ContainerCreate(ctx, &config, &hostConfig, &network.NetworkingConfig{}, "")
assert.NilError(t, err)
// container will exit immediately because of no tty, but we only need the start sequence to test the condition
err = cli.ContainerStart(ctx, ctrCreate.ID, types.ContainerStartOptions{})
assert.NilError(t, err)
// Check that host-located bind mount network file did not change ownership when the container was started
// Note: If the user specifies a mountpath from the host, we should not be
// attempting to chown files outside the daemon's metadata directory
// (represented by `daemon.repository` at init time).
// This forces users who want to use user namespaces to handle the
// ownership needs of any external files mounted as network files
// (/etc/resolv.conf, /etc/hosts, /etc/hostname) separately from the
// daemon. In all other volume/bind mount situations we have taken this
// same line--we don't chown host file content.
// See GitHub PR 34224 for details.
statT, err := system.Stat(tmpNWFileMount)
assert.NilError(t, err)
assert.Check(t, is.Equal(uint32(0), statT.UID()), "bind mounted network file should not change ownership from root")
}
func TestMountDaemonRoot(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
t.Parallel()
client := request.NewAPIClient(t)
ctx := context.Background()
info, err := client.Info(ctx)
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
desc string
propagation mount.Propagation
expected mount.Propagation
}{
{
desc: "default",
propagation: "",
expected: mount.PropagationRSlave,
},
{
desc: "private",
propagation: mount.PropagationPrivate,
},
{
desc: "rprivate",
propagation: mount.PropagationRPrivate,
},
{
desc: "slave",
propagation: mount.PropagationSlave,
},
{
desc: "rslave",
propagation: mount.PropagationRSlave,
expected: mount.PropagationRSlave,
},
{
desc: "shared",
propagation: mount.PropagationShared,
},
{
desc: "rshared",
propagation: mount.PropagationRShared,
expected: mount.PropagationRShared,
},
} {
t.Run(test.desc, func(t *testing.T) {
test := test
t.Parallel()
propagationSpec := fmt.Sprintf(":%s", test.propagation)
if test.propagation == "" {
propagationSpec = ""
}
bindSpecRoot := info.DockerRootDir + ":" + "/foo" + propagationSpec
bindSpecSub := filepath.Join(info.DockerRootDir, "containers") + ":/foo" + propagationSpec
for name, hc := range map[string]*container.HostConfig{
"bind root": {Binds: []string{bindSpecRoot}},
"bind subpath": {Binds: []string{bindSpecSub}},
"mount root": {
Mounts: []mount.Mount{
{
Type: mount.TypeBind,
Source: info.DockerRootDir,
Target: "/foo",
BindOptions: &mount.BindOptions{Propagation: test.propagation},
},
},
},
"mount subpath": {
Mounts: []mount.Mount{
{
Type: mount.TypeBind,
Source: filepath.Join(info.DockerRootDir, "containers"),
Target: "/foo",
BindOptions: &mount.BindOptions{Propagation: test.propagation},
},
},
},
} {
t.Run(name, func(t *testing.T) {
hc := hc
t.Parallel()
c, err := client.ContainerCreate(ctx, &container.Config{
Image: "busybox",
Cmd: []string{"true"},
}, hc, nil, "")
if err != nil {
if test.expected != "" {
t.Fatal(err)
}
// expected an error, so this is ok and should not continue
return
}
if test.expected == "" {
t.Fatal("expected create to fail")
}
defer func() {
if err := client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}); err != nil {
panic(err)
}
}()
inspect, err := client.ContainerInspect(ctx, c.ID)
if err != nil {
t.Fatal(err)
}
if len(inspect.Mounts) != 1 {
t.Fatalf("unexpected number of mounts: %+v", inspect.Mounts)
}
m := inspect.Mounts[0]
if m.Propagation != test.expected {
t.Fatalf("got unexpected propagation mode, expected %q, got: %v", test.expected, m.Propagation)
}
})
}
})
}
}

View file

@ -0,0 +1,120 @@
package container // import "github.com/docker/docker/integration/container"
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"net"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/go-connections/nat"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestNetworkNat(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
msg := "it works"
startServerContainer(t, msg, 8080)
endpoint := getExternalAddress(t)
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", endpoint.String(), 8080))
assert.NilError(t, err)
defer conn.Close()
data, err := ioutil.ReadAll(conn)
assert.NilError(t, err)
assert.Check(t, is.Equal(msg, strings.TrimSpace(string(data))))
}
func TestNetworkLocalhostTCPNat(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
msg := "hi yall"
startServerContainer(t, msg, 8081)
conn, err := net.Dial("tcp", "localhost:8081")
assert.NilError(t, err)
defer conn.Close()
data, err := ioutil.ReadAll(conn)
assert.NilError(t, err)
assert.Check(t, is.Equal(msg, strings.TrimSpace(string(data))))
}
func TestNetworkLoopbackNat(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
msg := "it works"
serverContainerID := startServerContainer(t, msg, 8080)
endpoint := getExternalAddress(t)
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, container.WithCmd("sh", "-c", fmt.Sprintf("stty raw && nc -w 5 %s 8080", endpoint.String())), container.WithTty(true), container.WithNetworkMode("container:"+serverContainerID))
poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
body, err := client.ContainerLogs(ctx, cID, types.ContainerLogsOptions{
ShowStdout: true,
})
assert.NilError(t, err)
defer body.Close()
var b bytes.Buffer
_, err = io.Copy(&b, body)
assert.NilError(t, err)
assert.Check(t, is.Equal(msg, strings.TrimSpace(b.String())))
}
func startServerContainer(t *testing.T, msg string, port int) string {
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, container.WithName("server-"+t.Name()), container.WithCmd("sh", "-c", fmt.Sprintf("echo %q | nc -lp %d", msg, port)), container.WithExposedPorts(fmt.Sprintf("%d/tcp", port)), func(c *container.TestContainerConfig) {
c.HostConfig.PortBindings = nat.PortMap{
nat.Port(fmt.Sprintf("%d/tcp", port)): []nat.PortBinding{
{
HostPort: fmt.Sprintf("%d", port),
},
},
}
})
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
return cID
}
func getExternalAddress(t *testing.T) net.IP {
iface, err := net.InterfaceByName("eth0")
skip.If(t, err != nil, "Test not running with `make test-integration`. Interface eth0 not found: %s", err)
ifaceAddrs, err := iface.Addrs()
assert.NilError(t, err)
assert.Check(t, 0 != len(ifaceAddrs))
ifaceIP, _, err := net.ParseCIDR(ifaceAddrs[0].String())
assert.NilError(t, err)
return ifaceIP
}

View file

@ -0,0 +1,98 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"io"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestPause(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows" && testEnv.DaemonInfo.Isolation == "process")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client)
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
since := request.DaemonUnixTime(ctx, t, client, testEnv)
err := client.ContainerPause(ctx, cID)
assert.NilError(t, err)
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal(true, inspect.State.Paused))
err = client.ContainerUnpause(ctx, cID)
assert.NilError(t, err)
until := request.DaemonUnixTime(ctx, t, client, testEnv)
messages, errs := client.Events(ctx, types.EventsOptions{
Since: since,
Until: until,
Filters: filters.NewArgs(filters.Arg("container", cID)),
})
assert.Check(t, is.DeepEqual([]string{"pause", "unpause"}, getEventActions(t, messages, errs)))
}
func TestPauseFailsOnWindowsServerContainers(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "windows" || testEnv.DaemonInfo.Isolation != "process")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client)
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerPause(ctx, cID)
assert.Check(t, is.ErrorContains(err, "cannot pause Windows Server Containers"))
}
func TestPauseStopPausedContainer(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.31"), "broken in earlier versions")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client)
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerPause(ctx, cID)
assert.NilError(t, err)
err = client.ContainerStop(ctx, cID, nil)
assert.NilError(t, err)
poll.WaitOn(t, container.IsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond))
}
func getEventActions(t *testing.T, messages <-chan events.Message, errs <-chan error) []string {
var actions []string
for {
select {
case err := <-errs:
assert.Check(t, err == nil || err == io.EOF)
return actions
case e := <-messages:
actions = append(actions, e.Status)
}
}
}

View file

@ -0,0 +1,49 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestPsFilter(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
prev := container.Create(t, ctx, client)
top := container.Create(t, ctx, client)
next := container.Create(t, ctx, client)
containerIDs := func(containers []types.Container) []string {
var entries []string
for _, container := range containers {
entries = append(entries, container.ID)
}
return entries
}
f1 := filters.NewArgs()
f1.Add("since", top)
q1, err := client.ContainerList(ctx, types.ContainerListOptions{
All: true,
Filters: f1,
})
assert.NilError(t, err)
assert.Check(t, is.Contains(containerIDs(q1), next))
f2 := filters.NewArgs()
f2.Add("before", top)
q2, err := client.ContainerList(ctx, types.ContainerListOptions{
All: true,
Filters: f2,
})
assert.NilError(t, err)
assert.Check(t, is.Contains(containerIDs(q2), prev))
}

View file

@ -0,0 +1,112 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"os"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/fs"
"gotest.tools/poll"
"gotest.tools/skip"
)
func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
if testEnv.OSType == "windows" {
return "c:", `\`
}
return "", "/"
}
// Test case for #5244: `docker rm` fails if bind dir doesn't exist anymore
func TestRemoveContainerWithRemovedVolume(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
prefix, slash := getPrefixAndSlashFromDaemonPlatform()
tempDir := fs.NewDir(t, "test-rm-container-with-removed-volume", fs.WithMode(0755))
defer tempDir.Remove()
cID := container.Run(t, ctx, client, container.WithCmd("true"), container.WithBind(tempDir.Path(), prefix+slash+"test"))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
err := os.RemoveAll(tempDir.Path())
assert.NilError(t, err)
err = client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{
RemoveVolumes: true,
})
assert.NilError(t, err)
_, _, err = client.ContainerInspectWithRaw(ctx, cID, true)
assert.Check(t, is.ErrorContains(err, "No such container"))
}
// Test case for #2099/#2125
func TestRemoveContainerWithVolume(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
prefix, slash := getPrefixAndSlashFromDaemonPlatform()
cID := container.Run(t, ctx, client, container.WithCmd("true"), container.WithVolume(prefix+slash+"srv"))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
insp, _, err := client.ContainerInspectWithRaw(ctx, cID, true)
assert.NilError(t, err)
assert.Check(t, is.Equal(1, len(insp.Mounts)))
volName := insp.Mounts[0].Name
err = client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{
RemoveVolumes: true,
})
assert.NilError(t, err)
volumes, err := client.VolumeList(ctx, filters.NewArgs(filters.Arg("name", volName)))
assert.NilError(t, err)
assert.Check(t, is.Equal(0, len(volumes.Volumes)))
}
func TestRemoveContainerRunning(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
cID := container.Run(t, ctx, client)
err := client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{})
assert.Check(t, is.ErrorContains(err, "cannot remove a running container"))
}
func TestRemoveContainerForceRemoveRunning(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
cID := container.Run(t, ctx, client)
err := client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{
Force: true,
})
assert.NilError(t, err)
}
func TestRemoveInvalidContainer(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
err := client.ContainerRemove(ctx, "unknown", types.ContainerRemoveOptions{})
assert.Check(t, is.ErrorContains(err, "No such container"))
}

View file

@ -0,0 +1,213 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"testing"
"time"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/pkg/stringid"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
// This test simulates the scenario mentioned in #31392:
// Having two linked container, renaming the target and bringing a replacement
// and then deleting and recreating the source container linked to the new target.
// This checks that "rename" updates source container correctly and doesn't set it to null.
func TestRenameLinkedContainer(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.32"), "broken in earlier versions")
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
aName := "a0" + t.Name()
bName := "b0" + t.Name()
aID := container.Run(t, ctx, client, container.WithName(aName))
bID := container.Run(t, ctx, client, container.WithName(bName), container.WithLinks(aName))
err := client.ContainerRename(ctx, aID, "a1"+t.Name())
assert.NilError(t, err)
container.Run(t, ctx, client, container.WithName(aName))
err = client.ContainerRemove(ctx, bID, types.ContainerRemoveOptions{Force: true})
assert.NilError(t, err)
bID = container.Run(t, ctx, client, container.WithName(bName), container.WithLinks(aName))
inspect, err := client.ContainerInspect(ctx, bID)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual([]string{"/" + aName + ":/" + bName + "/" + aName}, inspect.HostConfig.Links))
}
func TestRenameStoppedContainer(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
oldName := "first_name" + t.Name()
cID := container.Run(t, ctx, client, container.WithName(oldName), container.WithCmd("sh"))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal("/"+oldName, inspect.Name))
newName := "new_name" + stringid.GenerateNonCryptoID()
err = client.ContainerRename(ctx, oldName, newName)
assert.NilError(t, err)
inspect, err = client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal("/"+newName, inspect.Name))
}
func TestRenameRunningContainerAndReuse(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
oldName := "first_name" + t.Name()
cID := container.Run(t, ctx, client, container.WithName(oldName))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
newName := "new_name" + stringid.GenerateNonCryptoID()
err := client.ContainerRename(ctx, oldName, newName)
assert.NilError(t, err)
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal("/"+newName, inspect.Name))
_, err = client.ContainerInspect(ctx, oldName)
assert.Check(t, is.ErrorContains(err, "No such container: "+oldName))
cID = container.Run(t, ctx, client, container.WithName(oldName))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
inspect, err = client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal("/"+oldName, inspect.Name))
}
func TestRenameInvalidName(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
oldName := "first_name" + t.Name()
cID := container.Run(t, ctx, client, container.WithName(oldName))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerRename(ctx, oldName, "new:invalid")
assert.Check(t, is.ErrorContains(err, "Invalid container name"))
inspect, err := client.ContainerInspect(ctx, oldName)
assert.NilError(t, err)
assert.Check(t, is.Equal(cID, inspect.ID))
}
// Test case for GitHub issue 22466
// Docker's service discovery works for named containers so
// ping to a named container should work, and an anonymous
// container without a name does not work with service discovery.
// However, an anonymous could be renamed to a named container.
// This test is to make sure once the container has been renamed,
// the service discovery for the (re)named container works.
func TestRenameAnonymousContainer(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
networkName := "network1" + t.Name()
_, err := client.NetworkCreate(ctx, networkName, types.NetworkCreate{})
assert.NilError(t, err)
cID := container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
c.NetworkingConfig.EndpointsConfig = map[string]*network.EndpointSettings{
networkName: {},
}
c.HostConfig.NetworkMode = containertypes.NetworkMode(networkName)
})
container1Name := "container1" + t.Name()
err = client.ContainerRename(ctx, cID, container1Name)
assert.NilError(t, err)
// Stop/Start the container to get registered
// FIXME(vdemeester) this is a really weird behavior as it fails otherwise
err = client.ContainerStop(ctx, container1Name, nil)
assert.NilError(t, err)
err = client.ContainerStart(ctx, container1Name, types.ContainerStartOptions{})
assert.NilError(t, err)
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
count := "-c"
if testEnv.OSType == "windows" {
count = "-n"
}
cID = container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
c.NetworkingConfig.EndpointsConfig = map[string]*network.EndpointSettings{
networkName: {},
}
c.HostConfig.NetworkMode = containertypes.NetworkMode(networkName)
}, container.WithCmd("ping", count, "1", container1Name))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal(0, inspect.State.ExitCode), "container %s exited with the wrong exitcode: %+v", cID, inspect)
}
// TODO: should be a unit test
func TestRenameContainerWithSameName(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
oldName := "old" + t.Name()
cID := container.Run(t, ctx, client, container.WithName(oldName))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerRename(ctx, oldName, oldName)
assert.Check(t, is.ErrorContains(err, "Renaming a container with the same name"))
err = client.ContainerRename(ctx, cID, oldName)
assert.Check(t, is.ErrorContains(err, "Renaming a container with the same name"))
}
// Test case for GitHub issue 23973
// When a container is being renamed, the container might
// be linked to another container. In that case, the meta data
// of the linked container should be updated so that the other
// container could still reference to the container that is renamed.
func TestRenameContainerWithLinkedContainer(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
db1Name := "db1" + t.Name()
db1ID := container.Run(t, ctx, client, container.WithName(db1Name))
poll.WaitOn(t, container.IsInState(ctx, client, db1ID, "running"), poll.WithDelay(100*time.Millisecond))
app1Name := "app1" + t.Name()
app2Name := "app2" + t.Name()
app1ID := container.Run(t, ctx, client, container.WithName(app1Name), container.WithLinks(db1Name+":/mysql"))
poll.WaitOn(t, container.IsInState(ctx, client, app1ID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerRename(ctx, app1Name, app2Name)
assert.NilError(t, err)
inspect, err := client.ContainerInspect(ctx, app2Name+"/mysql")
assert.NilError(t, err)
assert.Check(t, is.Equal(db1ID, inspect.ID))
}

View file

@ -0,0 +1,66 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"net/http"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
req "github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestResize(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client)
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerResize(ctx, cID, types.ResizeOptions{
Height: 40,
Width: 40,
})
assert.NilError(t, err)
}
func TestResizeWithInvalidSize(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.32"), "broken in earlier versions")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client)
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
endpoint := "/containers/" + cID + "/resize?h=foo&w=bar"
res, _, err := req.Post(endpoint)
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(http.StatusBadRequest, res.StatusCode))
}
func TestResizeWhenContainerNotStarted(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, container.WithCmd("echo"))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerResize(ctx, cID, types.ResizeOptions{
Height: 40,
Width: 40,
})
assert.Check(t, is.ErrorContains(err, "is not running"))
}

View file

@ -0,0 +1,114 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"fmt"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
"gotest.tools/skip"
)
func TestDaemonRestartKillContainers(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run")
type testCase struct {
desc string
config *container.Config
hostConfig *container.HostConfig
xRunning bool
xRunningLiveRestore bool
xStart bool
}
for _, c := range []testCase{
{
desc: "container without restart policy",
config: &container.Config{Image: "busybox", Cmd: []string{"top"}},
xRunningLiveRestore: true,
xStart: true,
},
{
desc: "container with restart=always",
config: &container.Config{Image: "busybox", Cmd: []string{"top"}},
hostConfig: &container.HostConfig{RestartPolicy: container.RestartPolicy{Name: "always"}},
xRunning: true,
xRunningLiveRestore: true,
xStart: true,
},
{
desc: "container created should not be restarted",
config: &container.Config{Image: "busybox", Cmd: []string{"top"}},
hostConfig: &container.HostConfig{RestartPolicy: container.RestartPolicy{Name: "always"}},
},
} {
for _, liveRestoreEnabled := range []bool{false, true} {
for fnName, stopDaemon := range map[string]func(*testing.T, *daemon.Daemon){
"kill-daemon": func(t *testing.T, d *daemon.Daemon) {
err := d.Kill()
assert.NilError(t, err)
},
"stop-daemon": func(t *testing.T, d *daemon.Daemon) {
d.Stop(t)
},
} {
t.Run(fmt.Sprintf("live-restore=%v/%s/%s", liveRestoreEnabled, c.desc, fnName), func(t *testing.T) {
c := c
liveRestoreEnabled := liveRestoreEnabled
stopDaemon := stopDaemon
t.Parallel()
d := daemon.New(t)
client, err := d.NewClient()
assert.NilError(t, err)
args := []string{"--iptables=false"}
if liveRestoreEnabled {
args = append(args, "--live-restore")
}
d.StartWithBusybox(t, args...)
defer d.Stop(t)
ctx := context.Background()
resp, err := client.ContainerCreate(ctx, c.config, c.hostConfig, nil, "")
assert.NilError(t, err)
defer client.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})
if c.xStart {
err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
assert.NilError(t, err)
}
stopDaemon(t, d)
d.Start(t, args...)
expected := c.xRunning
if liveRestoreEnabled {
expected = c.xRunningLiveRestore
}
var running bool
for i := 0; i < 30; i++ {
inspect, err := client.ContainerInspect(ctx, resp.ID)
assert.NilError(t, err)
running = inspect.State.Running
if running == expected {
break
}
time.Sleep(2 * time.Second)
}
assert.Equal(t, expected, running, "got unexpected running state, expected %v, got: %v", expected, running)
// TODO(cpuguy83): test pause states... this seems to be rather undefined currently
})
}
}
}
}

View file

@ -0,0 +1,43 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"encoding/json"
"io"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestStats(t *testing.T) {
skip.If(t, !testEnv.DaemonInfo.MemoryLimit)
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
info, err := client.Info(ctx)
assert.NilError(t, err)
cID := container.Run(t, ctx, client)
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
resp, err := client.ContainerStats(ctx, cID, false)
assert.NilError(t, err)
defer resp.Body.Close()
var v *types.Stats
err = json.NewDecoder(resp.Body).Decode(&v)
assert.NilError(t, err)
assert.Check(t, is.Equal(int64(v.MemoryStats.Limit), info.MemTotal))
err = json.NewDecoder(resp.Body).Decode(&v)
assert.Assert(t, is.ErrorContains(err, ""), io.EOF)
}

View file

@ -0,0 +1,127 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"fmt"
"strconv"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
"gotest.tools/icmd"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestStopContainerWithRestartPolicyAlways(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
names := []string{"verifyRestart1-" + t.Name(), "verifyRestart2-" + t.Name()}
for _, name := range names {
container.Run(t, ctx, client, container.WithName(name), container.WithCmd("false"), func(c *container.TestContainerConfig) {
c.HostConfig.RestartPolicy.Name = "always"
})
}
for _, name := range names {
poll.WaitOn(t, container.IsInState(ctx, client, name, "running", "restarting"), poll.WithDelay(100*time.Millisecond))
}
for _, name := range names {
err := client.ContainerStop(ctx, name, nil)
assert.NilError(t, err)
}
for _, name := range names {
poll.WaitOn(t, container.IsStopped(ctx, client, name), poll.WithDelay(100*time.Millisecond))
}
}
// TestStopContainerWithTimeout checks that ContainerStop with
// a timeout works as documented, i.e. in case of negative timeout
// waiting is not limited (issue #35311).
func TestStopContainerWithTimeout(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
testCmd := container.WithCmd("sh", "-c", "sleep 2 && exit 42")
testData := []struct {
doc string
timeout int
expectedExitCode int
}{
// In case container is forcefully killed, 137 is returned,
// otherwise the exit code from the above script
{
"zero timeout: expect forceful container kill",
0, 137,
},
{
"too small timeout: expect forceful container kill",
1, 137,
},
{
"big enough timeout: expect graceful container stop",
3, 42,
},
{
"unlimited timeout: expect graceful container stop",
-1, 42,
},
}
for _, d := range testData {
d := d
t.Run(strconv.Itoa(d.timeout), func(t *testing.T) {
t.Parallel()
id := container.Run(t, ctx, client, testCmd)
timeout := time.Duration(d.timeout) * time.Second
err := client.ContainerStop(ctx, id, &timeout)
assert.NilError(t, err)
poll.WaitOn(t, container.IsStopped(ctx, client, id),
poll.WithDelay(100*time.Millisecond))
inspect, err := client.ContainerInspect(ctx, id)
assert.NilError(t, err)
assert.Equal(t, inspect.State.ExitCode, d.expectedExitCode)
})
}
}
func TestDeleteDevicemapper(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.Driver != "devicemapper")
skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
id := container.Run(t, ctx, client, container.WithName("foo-"+t.Name()), container.WithCmd("echo"))
poll.WaitOn(t, container.IsStopped(ctx, client, id), poll.WithDelay(100*time.Millisecond))
inspect, err := client.ContainerInspect(ctx, id)
assert.NilError(t, err)
deviceID := inspect.GraphDriver.Data["DeviceId"]
// Find pool name from device name
deviceName := inspect.GraphDriver.Data["DeviceName"]
devicePrefix := deviceName[:strings.LastIndex(deviceName, "-")]
devicePool := fmt.Sprintf("/dev/mapper/%s-pool", devicePrefix)
result := icmd.RunCommand("dmsetup", "message", devicePool, "0", fmt.Sprintf("delete %s", deviceID))
result.Assert(t, icmd.Success)
err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{})
assert.NilError(t, err)
}

View file

@ -0,0 +1,107 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"strconv"
"strings"
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestUpdateMemory(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, !testEnv.DaemonInfo.MemoryLimit)
skip.If(t, !testEnv.DaemonInfo.SwapLimit)
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
c.HostConfig.Resources = containertypes.Resources{
Memory: 200 * 1024 * 1024,
}
})
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
const (
setMemory int64 = 314572800
setMemorySwap int64 = 524288000
)
_, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{
Resources: containertypes.Resources{
Memory: setMemory,
MemorySwap: setMemorySwap,
},
})
assert.NilError(t, err)
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal(setMemory, inspect.HostConfig.Memory))
assert.Check(t, is.Equal(setMemorySwap, inspect.HostConfig.MemorySwap))
res, err := container.Exec(ctx, client, cID,
[]string{"cat", "/sys/fs/cgroup/memory/memory.limit_in_bytes"})
assert.NilError(t, err)
assert.Assert(t, is.Len(res.Stderr(), 0))
assert.Equal(t, 0, res.ExitCode)
assert.Check(t, is.Equal(strconv.FormatInt(setMemory, 10), strings.TrimSpace(res.Stdout())))
res, err = container.Exec(ctx, client, cID,
[]string{"cat", "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"})
assert.NilError(t, err)
assert.Assert(t, is.Len(res.Stderr(), 0))
assert.Equal(t, 0, res.ExitCode)
assert.Check(t, is.Equal(strconv.FormatInt(setMemorySwap, 10), strings.TrimSpace(res.Stdout())))
}
func TestUpdateCPUQuota(t *testing.T) {
t.Parallel()
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client)
for _, test := range []struct {
desc string
update int64
}{
{desc: "some random value", update: 15000},
{desc: "a higher value", update: 20000},
{desc: "a lower value", update: 10000},
{desc: "unset value", update: -1},
} {
if _, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{
Resources: containertypes.Resources{
CPUQuota: test.update,
},
}); err != nil {
t.Fatal(err)
}
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal(test.update, inspect.HostConfig.CPUQuota))
res, err := container.Exec(ctx, client, cID,
[]string{"/bin/cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"})
assert.NilError(t, err)
assert.Assert(t, is.Len(res.Stderr(), 0))
assert.Equal(t, 0, res.ExitCode)
assert.Check(t, is.Equal(strconv.FormatInt(test.update, 10), strings.TrimSpace(res.Stdout())))
}
}

View file

@ -0,0 +1,64 @@
package container // import "github.com/docker/docker/integration/container"
import (
"context"
"testing"
"time"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
)
func TestUpdateRestartPolicy(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, container.WithCmd("sh", "-c", "sleep 1 && false"), func(c *container.TestContainerConfig) {
c.HostConfig.RestartPolicy = containertypes.RestartPolicy{
Name: "on-failure",
MaximumRetryCount: 3,
}
})
_, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{
RestartPolicy: containertypes.RestartPolicy{
Name: "on-failure",
MaximumRetryCount: 5,
},
})
assert.NilError(t, err)
timeout := 60 * time.Second
if testEnv.OSType == "windows" {
timeout = 180 * time.Second
}
poll.WaitOn(t, container.IsInState(ctx, client, cID, "exited"), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(timeout))
inspect, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
assert.Check(t, is.Equal(inspect.RestartCount, 5))
assert.Check(t, is.Equal(inspect.HostConfig.RestartPolicy.MaximumRetryCount, 5))
}
func TestUpdateRestartWithAutoRemove(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID := container.Run(t, ctx, client, func(c *container.TestContainerConfig) {
c.HostConfig.AutoRemove = true
})
_, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{
RestartPolicy: containertypes.RestartPolicy{
Name: "always",
},
})
assert.Check(t, is.ErrorContains(err, "Restart policy cannot be updated because AutoRemove is enabled for the container"))
}

View file

@ -0,0 +1,3 @@
// Package integration provides integrations tests for Moby (API).
// These tests require a daemon (dockerd for now) to run.
package integration // import "github.com/docker/docker/integration"

View file

@ -0,0 +1,48 @@
package image // import "github.com/docker/docker/integration/image"
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestCommitInheritsEnv(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.36"), "broken in earlier versions")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
cID1 := container.Create(t, ctx, client)
commitResp1, err := client.ContainerCommit(ctx, cID1, types.ContainerCommitOptions{
Changes: []string{"ENV PATH=/bin"},
Reference: "test-commit-image",
})
assert.NilError(t, err)
image1, _, err := client.ImageInspectWithRaw(ctx, commitResp1.ID)
assert.NilError(t, err)
expectedEnv1 := []string{"PATH=/bin"}
assert.Check(t, is.DeepEqual(expectedEnv1, image1.Config.Env))
cID2 := container.Create(t, ctx, client, container.WithImage(image1.ID))
commitResp2, err := client.ContainerCommit(ctx, cID2, types.ContainerCommitOptions{
Changes: []string{"ENV PATH=/usr/bin:$PATH"},
Reference: "test-commit-image",
})
assert.NilError(t, err)
image2, _, err := client.ImageInspectWithRaw(ctx, commitResp2.ID)
assert.NilError(t, err)
expectedEnv2 := []string{"PATH=/usr/bin:/bin"}
assert.Check(t, is.DeepEqual(expectedEnv2, image2.Config.Env))
}

View file

@ -0,0 +1,42 @@
package image // import "github.com/docker/docker/integration/image"
import (
"archive/tar"
"bytes"
"context"
"io"
"runtime"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
)
// Ensure we don't regress on CVE-2017-14992.
func TestImportExtremelyLargeImageWorks(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("effective test will be time out")
}
client := request.NewAPIClient(t)
// Construct an empty tar archive with about 8GB of junk padding at the
// end. This should not cause any crashes (the padding should be mostly
// ignored).
var tarBuffer bytes.Buffer
tw := tar.NewWriter(&tarBuffer)
if err := tw.Close(); err != nil {
t.Fatal(err)
}
imageRdr := io.MultiReader(&tarBuffer, io.LimitReader(testutil.DevZero, 8*1024*1024*1024))
_, err := client.ImageImport(context.Background(),
types.ImageImportSource{Source: imageRdr, SourceName: "-"},
"test1234:v42",
types.ImageImportOptions{})
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,33 @@
package image // import "github.com/docker/docker/integration/image"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,59 @@
package image // import "github.com/docker/docker/integration/image"
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestRemoveImageOrphaning(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
img := "test-container-orphaning"
// Create a container from busybox, and commit a small change so we have a new image
cID1 := container.Create(t, ctx, client, container.WithCmd(""))
commitResp1, err := client.ContainerCommit(ctx, cID1, types.ContainerCommitOptions{
Changes: []string{`ENTRYPOINT ["true"]`},
Reference: img,
})
assert.NilError(t, err)
// verifies that reference now points to first image
resp, _, err := client.ImageInspectWithRaw(ctx, img)
assert.NilError(t, err)
assert.Check(t, is.Equal(resp.ID, commitResp1.ID))
// Create a container from created image, and commit a small change with same reference name
cID2 := container.Create(t, ctx, client, container.WithImage(img), container.WithCmd(""))
commitResp2, err := client.ContainerCommit(ctx, cID2, types.ContainerCommitOptions{
Changes: []string{`LABEL Maintainer="Integration Tests"`},
Reference: img,
})
assert.NilError(t, err)
// verifies that reference now points to second image
resp, _, err = client.ImageInspectWithRaw(ctx, img)
assert.NilError(t, err)
assert.Check(t, is.Equal(resp.ID, commitResp2.ID))
// try to remove the image, should not error out.
_, err = client.ImageRemove(ctx, img, types.ImageRemoveOptions{})
assert.NilError(t, err)
// check if the first image is still there
resp, _, err = client.ImageInspectWithRaw(ctx, commitResp1.ID)
assert.NilError(t, err)
assert.Check(t, is.Equal(resp.ID, commitResp1.ID))
// check if the second image has been deleted
_, _, err = client.ImageInspectWithRaw(ctx, commitResp2.ID)
assert.Check(t, is.ErrorContains(err, "No such image:"))
}

View file

@ -0,0 +1,140 @@
package image // import "github.com/docker/docker/integration/image"
import (
"context"
"fmt"
"testing"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
// tagging a named image in a new unprefixed repo should work
func TestTagUnprefixedRepoByNameOrName(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
// By name
err := client.ImageTag(ctx, "busybox:latest", "testfoobarbaz")
assert.NilError(t, err)
// By ID
insp, _, err := client.ImageInspectWithRaw(ctx, "busybox")
assert.NilError(t, err)
err = client.ImageTag(ctx, insp.ID, "testfoobarbaz")
assert.NilError(t, err)
}
// ensure we don't allow the use of invalid repository names or tags; these tag operations should fail
// TODO (yongtang): Migrate to unit tests
func TestTagInvalidReference(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
invalidRepos := []string{"fo$z$", "Foo@3cc", "Foo$3", "Foo*3", "Fo^3", "Foo!3", "F)xcz(", "fo%asd", "FOO/bar"}
for _, repo := range invalidRepos {
err := client.ImageTag(ctx, "busybox", repo)
assert.Check(t, is.ErrorContains(err, "not a valid repository/tag"))
}
longTag := testutil.GenerateRandomAlphaOnlyString(121)
invalidTags := []string{"repo:fo$z$", "repo:Foo@3cc", "repo:Foo$3", "repo:Foo*3", "repo:Fo^3", "repo:Foo!3", "repo:%goodbye", "repo:#hashtagit", "repo:F)xcz(", "repo:-foo", "repo:..", longTag}
for _, repotag := range invalidTags {
err := client.ImageTag(ctx, "busybox", repotag)
assert.Check(t, is.ErrorContains(err, "not a valid repository/tag"))
}
// test repository name begin with '-'
err := client.ImageTag(ctx, "busybox:latest", "-busybox:test")
assert.Check(t, is.ErrorContains(err, "Error parsing reference"))
// test namespace name begin with '-'
err = client.ImageTag(ctx, "busybox:latest", "-test/busybox:test")
assert.Check(t, is.ErrorContains(err, "Error parsing reference"))
// test index name begin with '-'
err = client.ImageTag(ctx, "busybox:latest", "-index:5000/busybox:test")
assert.Check(t, is.ErrorContains(err, "Error parsing reference"))
// test setting tag fails
err = client.ImageTag(ctx, "busybox:latest", "sha256:sometag")
assert.Check(t, is.ErrorContains(err, "refusing to create an ambiguous tag using digest algorithm as name"))
}
// ensure we allow the use of valid tags
func TestTagValidPrefixedRepo(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
validRepos := []string{"fooo/bar", "fooaa/test", "foooo:t", "HOSTNAME.DOMAIN.COM:443/foo/bar"}
for _, repo := range validRepos {
err := client.ImageTag(ctx, "busybox", repo)
assert.NilError(t, err)
}
}
// tag an image with an existed tag name without -f option should work
func TestTagExistedNameWithoutForce(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
err := client.ImageTag(ctx, "busybox:latest", "busybox:test")
assert.NilError(t, err)
}
// ensure tagging using official names works
// ensure all tags result in the same name
func TestTagOfficialNames(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
names := []string{
"docker.io/busybox",
"index.docker.io/busybox",
"library/busybox",
"docker.io/library/busybox",
"index.docker.io/library/busybox",
}
for _, name := range names {
err := client.ImageTag(ctx, "busybox", name+":latest")
assert.NilError(t, err)
// ensure we don't have multiple tag names.
insp, _, err := client.ImageInspectWithRaw(ctx, "busybox")
assert.NilError(t, err)
assert.Assert(t, !is.Contains(insp.RepoTags, name)().Success())
}
for _, name := range names {
err := client.ImageTag(ctx, name+":latest", "fooo/bar:latest")
assert.NilError(t, err)
}
}
// ensure tags can not match digests
func TestTagMatchesDigest(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
digest := "busybox@sha256:abcdef76720241213f5303bda7704ec4c2ef75613173910a56fb1b6e20251507"
// test setting tag fails
err := client.ImageTag(ctx, "busybox:latest", digest)
assert.Check(t, is.ErrorContains(err, "refusing to create a tag with a digest reference"))
// check that no new image matches the digest
_, _, err = client.ImageInspectWithRaw(ctx, digest)
assert.Check(t, is.ErrorContains(err, fmt.Sprintf("No such image: %s", digest)))
}

View file

@ -0,0 +1,56 @@
package container
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"gotest.tools/assert"
)
// TestContainerConfig holds container configuration struct that
// are used in api calls.
type TestContainerConfig struct {
Name string
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
}
// Create creates a container with the specified options
// nolint: golint
func Create(t *testing.T, ctx context.Context, client client.APIClient, ops ...func(*TestContainerConfig)) string { // nolint: golint
t.Helper()
config := &TestContainerConfig{
Config: &container.Config{
Image: "busybox",
Cmd: []string{"top"},
},
HostConfig: &container.HostConfig{},
NetworkingConfig: &network.NetworkingConfig{},
}
for _, op := range ops {
op(config)
}
c, err := client.ContainerCreate(ctx, config.Config, config.HostConfig, config.NetworkingConfig, config.Name)
assert.NilError(t, err)
return c.ID
}
// Run creates and start a container with the specified options
// nolint: golint
func Run(t *testing.T, ctx context.Context, client client.APIClient, ops ...func(*TestContainerConfig)) string { // nolint: golint
t.Helper()
id := Create(t, ctx, client, ops...)
err := client.ContainerStart(ctx, id, types.ContainerStartOptions{})
assert.NilError(t, err)
return id
}

View file

@ -0,0 +1,86 @@
package container
import (
"bytes"
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
)
// ExecResult represents a result returned from Exec()
type ExecResult struct {
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
}

View file

@ -0,0 +1,136 @@
package container
import (
"fmt"
containertypes "github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-connections/nat"
)
// WithName sets the name of the container
func WithName(name string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.Name = name
}
}
// WithLinks sets the links of the container
func WithLinks(links ...string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.HostConfig.Links = links
}
}
// WithImage sets the image of the container
func WithImage(image string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.Config.Image = image
}
}
// WithCmd sets the comannds of the container
func WithCmd(cmds ...string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.Config.Cmd = strslice.StrSlice(cmds)
}
}
// WithNetworkMode sets the network mode of the container
func WithNetworkMode(mode string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.HostConfig.NetworkMode = containertypes.NetworkMode(mode)
}
}
// WithExposedPorts sets the exposed ports of the container
func WithExposedPorts(ports ...string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.Config.ExposedPorts = map[nat.Port]struct{}{}
for _, port := range ports {
c.Config.ExposedPorts[nat.Port(port)] = struct{}{}
}
}
}
// WithTty sets the TTY mode of the container
func WithTty(tty bool) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.Config.Tty = tty
}
}
// WithWorkingDir sets the working dir of the container
func WithWorkingDir(dir string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.Config.WorkingDir = dir
}
}
// WithVolume sets the volume of the container
func WithVolume(name string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
if c.Config.Volumes == nil {
c.Config.Volumes = map[string]struct{}{}
}
c.Config.Volumes[name] = struct{}{}
}
}
// WithBind sets the bind mount of the container
func WithBind(src, target string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
c.HostConfig.Binds = append(c.HostConfig.Binds, fmt.Sprintf("%s:%s", src, target))
}
}
// WithIPv4 sets the specified ip for the specified network of the container
func WithIPv4(network, ip string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
if c.NetworkingConfig.EndpointsConfig == nil {
c.NetworkingConfig.EndpointsConfig = map[string]*networktypes.EndpointSettings{}
}
if v, ok := c.NetworkingConfig.EndpointsConfig[network]; !ok || v == nil {
c.NetworkingConfig.EndpointsConfig[network] = &networktypes.EndpointSettings{}
}
if c.NetworkingConfig.EndpointsConfig[network].IPAMConfig == nil {
c.NetworkingConfig.EndpointsConfig[network].IPAMConfig = &networktypes.EndpointIPAMConfig{}
}
c.NetworkingConfig.EndpointsConfig[network].IPAMConfig.IPv4Address = ip
}
}
// WithIPv6 sets the specified ip6 for the specified network of the container
func WithIPv6(network, ip string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
if c.NetworkingConfig.EndpointsConfig == nil {
c.NetworkingConfig.EndpointsConfig = map[string]*networktypes.EndpointSettings{}
}
if v, ok := c.NetworkingConfig.EndpointsConfig[network]; !ok || v == nil {
c.NetworkingConfig.EndpointsConfig[network] = &networktypes.EndpointSettings{}
}
if c.NetworkingConfig.EndpointsConfig[network].IPAMConfig == nil {
c.NetworkingConfig.EndpointsConfig[network].IPAMConfig = &networktypes.EndpointIPAMConfig{}
}
c.NetworkingConfig.EndpointsConfig[network].IPAMConfig.IPv6Address = ip
}
}
// WithLogDriver sets the log driver to use for the container
func WithLogDriver(driver string) func(*TestContainerConfig) {
return func(c *TestContainerConfig) {
if c.HostConfig == nil {
c.HostConfig = &containertypes.HostConfig{}
}
c.HostConfig.LogConfig.Type = driver
}
}
// WithAutoRemove sets the container to be removed on exit
func WithAutoRemove(c *TestContainerConfig) {
if c.HostConfig == nil {
c.HostConfig = &containertypes.HostConfig{}
}
c.HostConfig.AutoRemove = true
}

View file

@ -0,0 +1,41 @@
package container
import (
"context"
"strings"
"github.com/docker/docker/client"
"gotest.tools/poll"
)
// IsStopped verifies the container is in stopped state.
func IsStopped(ctx context.Context, client client.APIClient, containerID string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
inspect, err := client.ContainerInspect(ctx, containerID)
switch {
case err != nil:
return poll.Error(err)
case !inspect.State.Running:
return poll.Success()
default:
return poll.Continue("waiting for container to be stopped")
}
}
}
// IsInState verifies the container is in one of the specified state, e.g., "running", "exited", etc.
func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
inspect, err := client.ContainerInspect(ctx, containerID)
if err != nil {
return poll.Error(err)
}
for _, v := range state {
if inspect.State.Status == v {
return poll.Success()
}
}
return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status)
}
}

View file

@ -0,0 +1,36 @@
package network
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"gotest.tools/assert"
)
func createNetwork(ctx context.Context, client client.APIClient, name string, ops ...func(*types.NetworkCreate)) (string, error) {
config := types.NetworkCreate{}
for _, op := range ops {
op(&config)
}
n, err := client.NetworkCreate(ctx, name, config)
return n.ID, err
}
// Create creates a network with the specified options
func Create(ctx context.Context, client client.APIClient, name string, ops ...func(*types.NetworkCreate)) (string, error) {
return createNetwork(ctx, client, name, ops...)
}
// CreateNoError creates a network with the specified options and verifies there were no errors
// nolint: golint
func CreateNoError(t *testing.T, ctx context.Context, client client.APIClient, name string, ops ...func(*types.NetworkCreate)) string { // nolint: golint
t.Helper()
name, err := createNetwork(ctx, client, name, ops...)
assert.NilError(t, err)
return name
}

View file

@ -0,0 +1,94 @@
package network
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
)
// WithDriver sets the driver of the network
func WithDriver(driver string) func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
n.Driver = driver
}
}
// WithIPv6 Enables IPv6 on the network
func WithIPv6() func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
n.EnableIPv6 = true
}
}
// WithCheckDuplicate sets the CheckDuplicate field on create network request
func WithCheckDuplicate() func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
n.CheckDuplicate = true
}
}
// WithInternal enables Internal flag on the create network request
func WithInternal() func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
n.Internal = true
}
}
// WithAttachable sets Attachable flag on the create network request
func WithAttachable() func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
n.Attachable = true
}
}
// WithMacvlan sets the network as macvlan with the specified parent
func WithMacvlan(parent string) func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
n.Driver = "macvlan"
if parent != "" {
n.Options = map[string]string{
"parent": parent,
}
}
}
}
// WithIPvlan sets the network as ipvlan with the specified parent and mode
func WithIPvlan(parent, mode string) func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
n.Driver = "ipvlan"
if n.Options == nil {
n.Options = map[string]string{}
}
if parent != "" {
n.Options["parent"] = parent
}
if mode != "" {
n.Options["ipvlan_mode"] = mode
}
}
}
// WithOption adds the specified key/value pair to network's options
func WithOption(key, value string) func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
if n.Options == nil {
n.Options = map[string]string{}
}
n.Options[key] = value
}
}
// WithIPAM adds an IPAM with the specified Subnet and Gateway to the network
func WithIPAM(subnet, gateway string) func(*types.NetworkCreate) {
return func(n *types.NetworkCreate) {
if n.IPAM == nil {
n.IPAM = &network.IPAM{}
}
n.IPAM.Config = append(n.IPAM.Config, network.IPAMConfig{
Subnet: subnet,
Gateway: gateway,
AuxAddress: map[string]string{},
})
}
}

View file

@ -0,0 +1,53 @@
package requirement // import "github.com/docker/docker/integration/internal/requirement"
import (
"net/http"
"strings"
"testing"
"time"
"github.com/docker/docker/pkg/parsers/kernel"
"gotest.tools/icmd"
)
// HasHubConnectivity checks to see if https://hub.docker.com is
// accessible from the present environment
func HasHubConnectivity(t *testing.T) bool {
t.Helper()
// Set a timeout on the GET at 15s
var timeout = 15 * time.Second
var url = "https://hub.docker.com"
client := http.Client{Timeout: timeout}
resp, err := client.Get(url)
if err != nil && strings.Contains(err.Error(), "use of closed network connection") {
t.Fatalf("Timeout for GET request on %s", url)
}
if resp != nil {
resp.Body.Close()
}
return err == nil
}
func overlayFSSupported() bool {
result := icmd.RunCommand("/bin/sh", "-c", "cat /proc/filesystems")
if result.Error != nil {
return false
}
return strings.Contains(result.Combined(), "overlay\n")
}
// Overlay2Supported returns true if the current system supports overlay2 as graphdriver
func Overlay2Supported(kernelVersion string) bool {
if !overlayFSSupported() {
return false
}
daemonV, err := kernel.ParseRelease(kernelVersion)
if err != nil {
return false
}
requiredV := kernel.VersionInfo{Kernel: 4}
return kernel.CompareKernelVersion(*daemonV, requiredV) > -1
}

View file

@ -0,0 +1,200 @@
package swarm
import (
"context"
"runtime"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/internal/test/environment"
"gotest.tools/assert"
"gotest.tools/poll"
"gotest.tools/skip"
)
// ServicePoll tweaks the pollSettings for `service`
func ServicePoll(config *poll.Settings) {
// Override the default pollSettings for `service` resource here ...
config.Timeout = 30 * time.Second
config.Delay = 100 * time.Millisecond
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
config.Timeout = 90 * time.Second
}
}
// NetworkPoll tweaks the pollSettings for `network`
func NetworkPoll(config *poll.Settings) {
// Override the default pollSettings for `network` resource here ...
config.Timeout = 30 * time.Second
config.Delay = 100 * time.Millisecond
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
config.Timeout = 50 * time.Second
}
}
// ContainerPoll tweaks the pollSettings for `container`
func ContainerPoll(config *poll.Settings) {
// Override the default pollSettings for `container` resource here ...
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
config.Timeout = 30 * time.Second
config.Delay = 100 * time.Millisecond
}
}
// NewSwarm creates a swarm daemon for testing
func NewSwarm(t *testing.T, testEnv *environment.Execution, ops ...func(*daemon.Daemon)) *daemon.Daemon {
t.Helper()
skip.If(t, testEnv.IsRemoteDaemon)
if testEnv.DaemonInfo.ExperimentalBuild {
ops = append(ops, daemon.WithExperimental)
}
d := daemon.New(t, ops...)
d.StartAndSwarmInit(t)
return d
}
// ServiceSpecOpt is used with `CreateService` to pass in service spec modifiers
type ServiceSpecOpt func(*swarmtypes.ServiceSpec)
// CreateService creates a service on the passed in swarm daemon.
func CreateService(t *testing.T, d *daemon.Daemon, opts ...ServiceSpecOpt) string {
t.Helper()
spec := defaultServiceSpec()
for _, o := range opts {
o(&spec)
}
client := d.NewClientT(t)
defer client.Close()
resp, err := client.ServiceCreate(context.Background(), spec, types.ServiceCreateOptions{})
assert.NilError(t, err, "error creating service")
return resp.ID
}
func defaultServiceSpec() swarmtypes.ServiceSpec {
var spec swarmtypes.ServiceSpec
ServiceWithImage("busybox:latest")(&spec)
ServiceWithCommand([]string{"/bin/top"})(&spec)
ServiceWithReplicas(1)(&spec)
return spec
}
// ServiceWithInit sets whether the service should use init or not
func ServiceWithInit(b *bool) func(*swarmtypes.ServiceSpec) {
return func(spec *swarmtypes.ServiceSpec) {
ensureContainerSpec(spec)
spec.TaskTemplate.ContainerSpec.Init = b
}
}
// ServiceWithImage sets the image to use for the service
func ServiceWithImage(image string) func(*swarmtypes.ServiceSpec) {
return func(spec *swarmtypes.ServiceSpec) {
ensureContainerSpec(spec)
spec.TaskTemplate.ContainerSpec.Image = image
}
}
// ServiceWithCommand sets the command to use for the service
func ServiceWithCommand(cmd []string) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
ensureContainerSpec(spec)
spec.TaskTemplate.ContainerSpec.Command = cmd
}
}
// ServiceWithConfig adds the config reference to the service
func ServiceWithConfig(configRef *swarmtypes.ConfigReference) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
ensureContainerSpec(spec)
spec.TaskTemplate.ContainerSpec.Configs = append(spec.TaskTemplate.ContainerSpec.Configs, configRef)
}
}
// ServiceWithSecret adds the secret reference to the service
func ServiceWithSecret(secretRef *swarmtypes.SecretReference) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
ensureContainerSpec(spec)
spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, secretRef)
}
}
// ServiceWithReplicas sets the replicas for the service
func ServiceWithReplicas(n uint64) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
spec.Mode = swarmtypes.ServiceMode{
Replicated: &swarmtypes.ReplicatedService{
Replicas: &n,
},
}
}
}
// ServiceWithName sets the name of the service
func ServiceWithName(name string) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
spec.Annotations.Name = name
}
}
// ServiceWithNetwork sets the network of the service
func ServiceWithNetwork(network string) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
spec.TaskTemplate.Networks = append(spec.TaskTemplate.Networks,
swarmtypes.NetworkAttachmentConfig{Target: network})
}
}
// ServiceWithEndpoint sets the Endpoint of the service
func ServiceWithEndpoint(endpoint *swarmtypes.EndpointSpec) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
spec.EndpointSpec = endpoint
}
}
// GetRunningTasks gets the list of running tasks for a service
func GetRunningTasks(t *testing.T, d *daemon.Daemon, serviceID string) []swarmtypes.Task {
t.Helper()
client := d.NewClientT(t)
defer client.Close()
filterArgs := filters.NewArgs()
filterArgs.Add("desired-state", "running")
filterArgs.Add("service", serviceID)
options := types.TaskListOptions{
Filters: filterArgs,
}
tasks, err := client.TaskList(context.Background(), options)
assert.NilError(t, err)
return tasks
}
// ExecTask runs the passed in exec config on the given task
func ExecTask(t *testing.T, d *daemon.Daemon, task swarmtypes.Task, config types.ExecConfig) types.HijackedResponse {
t.Helper()
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
resp, err := client.ContainerExecCreate(ctx, task.Status.ContainerStatus.ContainerID, config)
assert.NilError(t, err, "error creating exec")
startCheck := types.ExecStartCheck{}
attach, err := client.ContainerExecAttach(ctx, resp.ID, startCheck)
assert.NilError(t, err, "error attaching to exec")
return attach
}
func ensureContainerSpec(spec *swarmtypes.ServiceSpec) {
if spec.TaskTemplate.ContainerSpec == nil {
spec.TaskTemplate.ContainerSpec = &swarmtypes.ContainerSpec{}
}
}

View file

@ -0,0 +1,91 @@
package network // import "github.com/docker/docker/integration/network"
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func containsNetwork(nws []types.NetworkResource, networkID string) bool {
for _, n := range nws {
if n.ID == networkID {
return true
}
}
return false
}
// createAmbiguousNetworks creates three networks, of which the second network
// uses a prefix of the first network's ID as name. The third network uses the
// first network's ID as name.
//
// After successful creation, properties of all three networks is returned
func createAmbiguousNetworks(t *testing.T) (string, string, string) {
client := request.NewAPIClient(t)
ctx := context.Background()
testNet := network.CreateNoError(t, ctx, client, "testNet")
idPrefixNet := network.CreateNoError(t, ctx, client, testNet[:12])
fullIDNet := network.CreateNoError(t, ctx, client, testNet)
nws, err := client.NetworkList(ctx, types.NetworkListOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(true, containsNetwork(nws, testNet)), "failed to create network testNet")
assert.Check(t, is.Equal(true, containsNetwork(nws, idPrefixNet)), "failed to create network idPrefixNet")
assert.Check(t, is.Equal(true, containsNetwork(nws, fullIDNet)), "failed to create network fullIDNet")
return testNet, idPrefixNet, fullIDNet
}
func TestNetworkCreateDelete(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
netName := "testnetwork_" + t.Name()
network.CreateNoError(t, ctx, client, netName,
network.WithCheckDuplicate(),
)
assert.Check(t, IsNetworkAvailable(client, netName))
// delete the network and make sure it is deleted
err := client.NetworkRemove(ctx, netName)
assert.NilError(t, err)
assert.Check(t, IsNetworkNotAvailable(client, netName))
}
// TestDockerNetworkDeletePreferID tests that if a network with a name
// equal to another network's ID exists, the Network with the given
// ID is removed, and not the network with the given name.
func TestDockerNetworkDeletePreferID(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.34"), "broken in earlier versions")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
testNet, idPrefixNet, fullIDNet := createAmbiguousNetworks(t)
// Delete the network using a prefix of the first network's ID as name.
// This should the network name with the id-prefix, not the original network.
err := client.NetworkRemove(ctx, testNet[:12])
assert.NilError(t, err)
// Delete the network using networkID. This should remove the original
// network, not the network with the name equal to the networkID
err = client.NetworkRemove(ctx, testNet)
assert.NilError(t, err)
// networks "testNet" and "idPrefixNet" should be removed, but "fullIDNet" should still exist
nws, err := client.NetworkList(ctx, types.NetworkListOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(false, containsNetwork(nws, testNet)), "Network testNet not removed")
assert.Check(t, is.Equal(false, containsNetwork(nws, idPrefixNet)), "Network idPrefixNet not removed")
assert.Check(t, is.Equal(true, containsNetwork(nws, fullIDNet)), "Network fullIDNet not found")
}

View file

@ -0,0 +1,92 @@
package network
import (
"context"
"fmt"
"os"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/parsers/kernel"
"gotest.tools/assert/cmp"
"gotest.tools/icmd"
)
// CreateMasterDummy creates a dummy network interface
func CreateMasterDummy(t *testing.T, master string) {
// ip link add <dummy_name> type dummy
icmd.RunCommand("ip", "link", "add", master, "type", "dummy").Assert(t, icmd.Success)
icmd.RunCommand("ip", "link", "set", master, "up").Assert(t, icmd.Success)
}
// CreateVlanInterface creates a vlan network interface
func CreateVlanInterface(t *testing.T, master, slave, id string) {
// ip link add link <master> name <master>.<VID> type vlan id <VID>
icmd.RunCommand("ip", "link", "add", "link", master, "name", slave, "type", "vlan", "id", id).Assert(t, icmd.Success)
// ip link set <sub_interface_name> up
icmd.RunCommand("ip", "link", "set", slave, "up").Assert(t, icmd.Success)
}
// DeleteInterface deletes a network interface
func DeleteInterface(t *testing.T, ifName string) {
icmd.RunCommand("ip", "link", "delete", ifName).Assert(t, icmd.Success)
icmd.RunCommand("iptables", "-t", "nat", "--flush").Assert(t, icmd.Success)
icmd.RunCommand("iptables", "--flush").Assert(t, icmd.Success)
}
// LinkExists verifies that a link exists
func LinkExists(t *testing.T, master string) {
// verify the specified link exists, ip link show <link_name>
icmd.RunCommand("ip", "link", "show", master).Assert(t, icmd.Success)
}
// IsNetworkAvailable provides a comparison to check if a docker network is available
func IsNetworkAvailable(c client.NetworkAPIClient, name string) cmp.Comparison {
return func() cmp.Result {
networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
if err != nil {
return cmp.ResultFromError(err)
}
for _, network := range networks {
if network.Name == name {
return cmp.ResultSuccess
}
}
return cmp.ResultFailure(fmt.Sprintf("could not find network %s", name))
}
}
// IsNetworkNotAvailable provides a comparison to check if a docker network is not available
func IsNetworkNotAvailable(c client.NetworkAPIClient, name string) cmp.Comparison {
return func() cmp.Result {
networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
if err != nil {
return cmp.ResultFromError(err)
}
for _, network := range networks {
if network.Name == name {
return cmp.ResultFailure(fmt.Sprintf("network %s is still present", name))
}
}
return cmp.ResultSuccess
}
}
// CheckKernelMajorVersionGreaterOrEqualThen returns whether the kernel version is greater or equal than the one provided
func CheckKernelMajorVersionGreaterOrEqualThen(kernelVersion int, majorVersion int) bool {
kv, err := kernel.GetKernelVersion()
if err != nil {
return false
}
if kv.Kernel < kernelVersion || (kv.Kernel == kernelVersion && kv.Major < majorVersion) {
return false
}
return true
}
// IsUserNamespace returns whether the user namespace remapping is enabled
func IsUserNamespace() bool {
root := os.Getenv("DOCKER_REMAP_ROOT")
return root != ""
}

View file

@ -0,0 +1,177 @@
package network // import "github.com/docker/docker/integration/network"
import (
"context"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/integration/internal/swarm"
"gotest.tools/assert"
"gotest.tools/poll"
)
const defaultSwarmPort = 2477
func TestInspectNetwork(t *testing.T) {
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
overlayName := "overlay1"
overlayID := network.CreateNoError(t, context.Background(), client, overlayName,
network.WithDriver("overlay"),
network.WithCheckDuplicate(),
)
var instances uint64 = 4
serviceName := "TestService" + t.Name()
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(serviceName),
swarm.ServiceWithNetwork(overlayName),
)
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, instances), swarm.ServicePoll)
_, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
assert.NilError(t, err)
// Test inspect verbose with full NetworkID
networkVerbose, err := client.NetworkInspect(context.Background(), overlayID, types.NetworkInspectOptions{
Verbose: true,
})
assert.NilError(t, err)
assert.Assert(t, validNetworkVerbose(networkVerbose, serviceName, instances))
// Test inspect verbose with partial NetworkID
networkVerbose, err = client.NetworkInspect(context.Background(), overlayID[0:11], types.NetworkInspectOptions{
Verbose: true,
})
assert.NilError(t, err)
assert.Assert(t, validNetworkVerbose(networkVerbose, serviceName, instances))
// Test inspect verbose with Network name and swarm scope
networkVerbose, err = client.NetworkInspect(context.Background(), overlayName, types.NetworkInspectOptions{
Verbose: true,
Scope: "swarm",
})
assert.NilError(t, err)
assert.Assert(t, validNetworkVerbose(networkVerbose, serviceName, instances))
err = client.ServiceRemove(context.Background(), serviceID)
assert.NilError(t, err)
poll.WaitOn(t, serviceIsRemoved(client, serviceID), swarm.ServicePoll)
poll.WaitOn(t, noTasks(client), swarm.ServicePoll)
serviceID2 := swarm.CreateService(t, d,
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(serviceName),
swarm.ServiceWithNetwork(overlayName),
)
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID2, instances), swarm.ServicePoll)
err = client.ServiceRemove(context.Background(), serviceID2)
assert.NilError(t, err)
poll.WaitOn(t, serviceIsRemoved(client, serviceID2), swarm.ServicePoll)
poll.WaitOn(t, noTasks(client), swarm.ServicePoll)
err = client.NetworkRemove(context.Background(), overlayID)
assert.NilError(t, err)
poll.WaitOn(t, networkIsRemoved(client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
}
func serviceRunningTasksCount(client client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
filter := filters.NewArgs()
filter.Add("service", serviceID)
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filter,
})
switch {
case err != nil:
return poll.Error(err)
case len(tasks) == int(instances):
for _, task := range tasks {
if task.Status.State != swarmtypes.TaskStateRunning {
return poll.Continue("waiting for tasks to enter run state")
}
}
return poll.Success()
default:
return poll.Continue("task count at %d waiting for %d", len(tasks), instances)
}
}
}
func networkIsRemoved(client client.NetworkAPIClient, networkID string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
_, err := client.NetworkInspect(context.Background(), networkID, types.NetworkInspectOptions{})
if err == nil {
return poll.Continue("waiting for network %s to be removed", networkID)
}
return poll.Success()
}
}
func serviceIsRemoved(client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
filter := filters.NewArgs()
filter.Add("service", serviceID)
_, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filter,
})
if err == nil {
return poll.Continue("waiting for service %s to be deleted", serviceID)
}
return poll.Success()
}
}
func noTasks(client client.ServiceAPIClient) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
filter := filters.NewArgs()
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filter,
})
switch {
case err != nil:
return poll.Error(err)
case len(tasks) == 0:
return poll.Success()
default:
return poll.Continue("task count at %d waiting for 0", len(tasks))
}
}
}
// Check to see if Service and Tasks info are part of the inspect verbose response
func validNetworkVerbose(network types.NetworkResource, service string, instances uint64) bool {
if service, ok := network.Services[service]; ok {
if len(service.Tasks) != int(instances) {
return false
}
}
if network.IPAM.Config == nil {
return false
}
for _, cfg := range network.IPAM.Config {
if cfg.Gateway == "" || cfg.Subnet == "" {
return false
}
}
return true
}

View file

@ -0,0 +1,432 @@
package ipvlan
import (
"context"
"strings"
"testing"
"time"
dclient "github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
net "github.com/docker/docker/integration/internal/network"
n "github.com/docker/docker/integration/network"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
"gotest.tools/skip"
)
func TestDockerNetworkIpvlanPersistance(t *testing.T) {
// verify the driver automatically provisions the 802.1q link (di-dummy0.70)
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, !ipvlanKernelSupport(), "Kernel doesn't support ipvlan")
d := daemon.New(t, daemon.WithExperimental)
d.StartWithBusybox(t)
defer d.Stop(t)
// master dummy interface 'di' notation represent 'docker ipvlan'
master := "di-dummy0"
n.CreateMasterDummy(t, master)
defer n.DeleteInterface(t, master)
client, err := d.NewClient()
assert.NilError(t, err)
// create a network specifying the desired sub-interface name
netName := "di-persist"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan("di-dummy0.70", ""),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
// Restart docker daemon to test the config has persisted to disk
d.Restart(t)
assert.Check(t, n.IsNetworkAvailable(client, netName))
}
func TestDockerNetworkIpvlan(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, !ipvlanKernelSupport(), "Kernel doesn't support ipvlan")
for _, tc := range []struct {
name string
test func(dclient.APIClient) func(*testing.T)
}{
{
name: "Subinterface",
test: testIpvlanSubinterface,
}, {
name: "OverlapParent",
test: testIpvlanOverlapParent,
}, {
name: "L2NilParent",
test: testIpvlanL2NilParent,
}, {
name: "L2InternalMode",
test: testIpvlanL2InternalMode,
}, {
name: "L3NilParent",
test: testIpvlanL3NilParent,
}, {
name: "L3InternalMode",
test: testIpvlanL3InternalMode,
}, {
name: "L2MultiSubnet",
test: testIpvlanL2MultiSubnet,
}, {
name: "L3MultiSubnet",
test: testIpvlanL3MultiSubnet,
}, {
name: "Addressing",
test: testIpvlanAddressing,
},
} {
d := daemon.New(t, daemon.WithExperimental)
d.StartWithBusybox(t)
client, err := d.NewClient()
assert.NilError(t, err)
t.Run(tc.name, tc.test(client))
d.Stop(t)
// FIXME(vdemeester) clean network
}
}
func testIpvlanSubinterface(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
master := "di-dummy0"
n.CreateMasterDummy(t, master)
defer n.DeleteInterface(t, master)
netName := "di-subinterface"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan("di-dummy0.60", ""),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
// delete the network while preserving the parent link
err := client.NetworkRemove(context.Background(), netName)
assert.NilError(t, err)
assert.Check(t, n.IsNetworkNotAvailable(client, netName))
// verify the network delete did not delete the predefined link
n.LinkExists(t, "di-dummy0")
}
}
func testIpvlanOverlapParent(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
// verify the same parent interface cannot be used if already in use by an existing network
master := "di-dummy0"
parent := master + ".30"
n.CreateMasterDummy(t, master)
defer n.DeleteInterface(t, master)
n.CreateVlanInterface(t, master, parent, "30")
netName := "di-subinterface"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan(parent, ""),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
_, err := net.Create(context.Background(), client, netName,
net.WithIPvlan(parent, ""),
)
// verify that the overlap returns an error
assert.Check(t, err != nil)
}
}
func testIpvlanL2NilParent(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
// ipvlan l2 mode - dummy parent interface is provisioned dynamically
netName := "di-nil-parent"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan("", ""),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
ctx := context.Background()
id1 := container.Run(t, ctx, client, container.WithNetworkMode(netName))
id2 := container.Run(t, ctx, client, container.WithNetworkMode(netName))
_, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
assert.NilError(t, err)
}
}
func testIpvlanL2InternalMode(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
netName := "di-internal"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan("", ""),
net.WithInternal(),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
ctx := context.Background()
id1 := container.Run(t, ctx, client, container.WithNetworkMode(netName))
id2 := container.Run(t, ctx, client, container.WithNetworkMode(netName))
timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
_, err := container.Exec(timeoutCtx, client, id1, []string{"ping", "-c", "1", "-w", "1", "8.8.8.8"})
// FIXME(vdemeester) check the time of error ?
assert.Check(t, err != nil)
assert.Check(t, timeoutCtx.Err() == context.DeadlineExceeded)
_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
assert.NilError(t, err)
}
}
func testIpvlanL3NilParent(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
netName := "di-nil-parent-l3"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan("", "l3"),
net.WithIPAM("172.28.230.0/24", ""),
net.WithIPAM("172.28.220.0/24", ""),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
ctx := context.Background()
id1 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.220.10"),
)
id2 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.230.10"),
)
_, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
assert.NilError(t, err)
}
}
func testIpvlanL3InternalMode(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
netName := "di-internal-l3"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan("", "l3"),
net.WithInternal(),
net.WithIPAM("172.28.230.0/24", ""),
net.WithIPAM("172.28.220.0/24", ""),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
ctx := context.Background()
id1 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.220.10"),
)
id2 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.230.10"),
)
timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
_, err := container.Exec(timeoutCtx, client, id1, []string{"ping", "-c", "1", "-w", "1", "8.8.8.8"})
// FIXME(vdemeester) check the time of error ?
assert.Check(t, err != nil)
assert.Check(t, timeoutCtx.Err() == context.DeadlineExceeded)
_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
assert.NilError(t, err)
}
}
func testIpvlanL2MultiSubnet(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
netName := "dualstackl2"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan("", ""),
net.WithIPv6(),
net.WithIPAM("172.28.200.0/24", ""),
net.WithIPAM("172.28.202.0/24", "172.28.202.254"),
net.WithIPAM("2001:db8:abc8::/64", ""),
net.WithIPAM("2001:db8:abc6::/64", "2001:db8:abc6::254"),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.100.0/24 and 2001:db8:abc2::/64
ctx := context.Background()
id1 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.200.20"),
container.WithIPv6(netName, "2001:db8:abc8::20"),
)
id2 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.200.21"),
container.WithIPv6(netName, "2001:db8:abc8::21"),
)
c1, err := client.ContainerInspect(ctx, id1)
assert.NilError(t, err)
// verify ipv4 connectivity to the explicit --ipv address second to first
_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
assert.NilError(t, err)
// verify ipv6 connectivity to the explicit --ipv6 address second to first
_, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
assert.NilError(t, err)
// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64
id3 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.202.20"),
container.WithIPv6(netName, "2001:db8:abc6::20"),
)
id4 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.202.21"),
container.WithIPv6(netName, "2001:db8:abc6::21"),
)
c3, err := client.ContainerInspect(ctx, id3)
assert.NilError(t, err)
// verify ipv4 connectivity to the explicit --ipv address from third to fourth
_, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
assert.NilError(t, err)
// verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
_, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
assert.NilError(t, err)
// Inspect the v4 gateway to ensure the proper default GW was assigned
assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "172.28.200.1")
// Inspect the v6 gateway to ensure the proper default GW was assigned
assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc8::1")
// Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "172.28.202.254")
// Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "2001:db8:abc6::254")
}
}
func testIpvlanL3MultiSubnet(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
netName := "dualstackl3"
net.CreateNoError(t, context.Background(), client, netName,
net.WithIPvlan("", "l3"),
net.WithIPv6(),
net.WithIPAM("172.28.10.0/24", ""),
net.WithIPAM("172.28.12.0/24", "172.28.12.254"),
net.WithIPAM("2001:db8:abc9::/64", ""),
net.WithIPAM("2001:db8:abc7::/64", "2001:db8:abc7::254"),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.100.0/24 and 2001:db8:abc2::/64
ctx := context.Background()
id1 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.10.20"),
container.WithIPv6(netName, "2001:db8:abc9::20"),
)
id2 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.10.21"),
container.WithIPv6(netName, "2001:db8:abc9::21"),
)
c1, err := client.ContainerInspect(ctx, id1)
assert.NilError(t, err)
// verify ipv4 connectivity to the explicit --ipv address second to first
_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks[netName].IPAddress})
assert.NilError(t, err)
// verify ipv6 connectivity to the explicit --ipv6 address second to first
_, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks[netName].GlobalIPv6Address})
assert.NilError(t, err)
// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64
id3 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.12.20"),
container.WithIPv6(netName, "2001:db8:abc7::20"),
)
id4 := container.Run(t, ctx, client,
container.WithNetworkMode(netName),
container.WithIPv4(netName, "172.28.12.21"),
container.WithIPv6(netName, "2001:db8:abc7::21"),
)
c3, err := client.ContainerInspect(ctx, id3)
assert.NilError(t, err)
// verify ipv4 connectivity to the explicit --ipv address from third to fourth
_, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks[netName].IPAddress})
assert.NilError(t, err)
// verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
_, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks[netName].GlobalIPv6Address})
assert.NilError(t, err)
// Inspect the v4 gateway to ensure no next hop is assigned in L3 mode
assert.Equal(t, c1.NetworkSettings.Networks[netName].Gateway, "")
// Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled
assert.Equal(t, c1.NetworkSettings.Networks[netName].IPv6Gateway, "")
// Inspect the v4 gateway to ensure no next hop is assigned in L3 mode
assert.Equal(t, c3.NetworkSettings.Networks[netName].Gateway, "")
// Inspect the v6 gateway to ensure the explicitly specified default GW is ignored per L3 mode enabled
assert.Equal(t, c3.NetworkSettings.Networks[netName].IPv6Gateway, "")
}
}
func testIpvlanAddressing(client dclient.APIClient) func(*testing.T) {
return func(t *testing.T) {
// Verify ipvlan l2 mode sets the proper default gateway routes via netlink
// for either an explicitly set route by the user or inferred via default IPAM
netNameL2 := "dualstackl2"
net.CreateNoError(t, context.Background(), client, netNameL2,
net.WithIPvlan("", "l2"),
net.WithIPv6(),
net.WithIPAM("172.28.140.0/24", "172.28.140.254"),
net.WithIPAM("2001:db8:abcb::/64", ""),
)
assert.Check(t, n.IsNetworkAvailable(client, netNameL2))
ctx := context.Background()
id1 := container.Run(t, ctx, client,
container.WithNetworkMode(netNameL2),
)
// Validate ipvlan l2 mode defaults gateway sets the default IPAM next-hop inferred from the subnet
result, err := container.Exec(ctx, client, id1, []string{"ip", "route"})
assert.NilError(t, err)
assert.Check(t, strings.Contains(result.Combined(), "default via 172.28.140.254 dev eth0"))
// Validate ipvlan l2 mode sets the v6 gateway to the user specified default gateway/next-hop
result, err = container.Exec(ctx, client, id1, []string{"ip", "-6", "route"})
assert.NilError(t, err)
assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abcb::1 dev eth0"))
// Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
netNameL3 := "dualstackl3"
net.CreateNoError(t, context.Background(), client, netNameL3,
net.WithIPvlan("", "l3"),
net.WithIPv6(),
net.WithIPAM("172.28.160.0/24", "172.28.160.254"),
net.WithIPAM("2001:db8:abcd::/64", "2001:db8:abcd::254"),
)
assert.Check(t, n.IsNetworkAvailable(client, netNameL3))
id2 := container.Run(t, ctx, client,
container.WithNetworkMode(netNameL3),
)
// Validate ipvlan l3 mode sets the v4 gateway to dev eth0 and disregards any explicit or inferred next-hops
result, err = container.Exec(ctx, client, id2, []string{"ip", "route"})
assert.NilError(t, err)
assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
// Validate ipvlan l3 mode sets the v6 gateway to dev eth0 and disregards any explicit or inferred next-hops
result, err = container.Exec(ctx, client, id2, []string{"ip", "-6", "route"})
assert.NilError(t, err)
assert.Check(t, strings.Contains(result.Combined(), "default dev eth0"))
}
}
// ensure Kernel version is >= v4.2 for ipvlan support
func ipvlanKernelSupport() bool {
return n.CheckKernelMajorVersionGreaterOrEqualThen(4, 2)
}

View file

@ -0,0 +1,33 @@
package ipvlan // import "github.com/docker/docker/integration/network/ipvlan"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,281 @@
package macvlan
import (
"context"
"strings"
"testing"
"time"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
net "github.com/docker/docker/integration/internal/network"
n "github.com/docker/docker/integration/network"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
"gotest.tools/skip"
)
func TestDockerNetworkMacvlanPersistance(t *testing.T) {
// verify the driver automatically provisions the 802.1q link (dm-dummy0.60)
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, !macvlanKernelSupport(), "Kernel doesn't support macvlan")
d := daemon.New(t)
d.StartWithBusybox(t)
defer d.Stop(t)
master := "dm-dummy0"
n.CreateMasterDummy(t, master)
defer n.DeleteInterface(t, master)
client, err := d.NewClient()
assert.NilError(t, err)
netName := "dm-persist"
net.CreateNoError(t, context.Background(), client, netName,
net.WithMacvlan("dm-dummy0.60"),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
d.Restart(t)
assert.Check(t, n.IsNetworkAvailable(client, netName))
}
func TestDockerNetworkMacvlan(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, !macvlanKernelSupport(), "Kernel doesn't support macvlan")
for _, tc := range []struct {
name string
test func(client.APIClient) func(*testing.T)
}{
{
name: "Subinterface",
test: testMacvlanSubinterface,
}, {
name: "OverlapParent",
test: testMacvlanOverlapParent,
}, {
name: "NilParent",
test: testMacvlanNilParent,
}, {
name: "InternalMode",
test: testMacvlanInternalMode,
}, {
name: "Addressing",
test: testMacvlanAddressing,
},
} {
d := daemon.New(t)
d.StartWithBusybox(t)
client, err := d.NewClient()
assert.NilError(t, err)
t.Run(tc.name, tc.test(client))
d.Stop(t)
// FIXME(vdemeester) clean network
}
}
func testMacvlanOverlapParent(client client.APIClient) func(*testing.T) {
return func(t *testing.T) {
// verify the same parent interface cannot be used if already in use by an existing network
master := "dm-dummy0"
n.CreateMasterDummy(t, master)
defer n.DeleteInterface(t, master)
netName := "dm-subinterface"
parentName := "dm-dummy0.40"
net.CreateNoError(t, context.Background(), client, netName,
net.WithMacvlan(parentName),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
_, err := net.Create(context.Background(), client, "dm-parent-net-overlap",
net.WithMacvlan(parentName),
)
assert.Check(t, err != nil)
// delete the network while preserving the parent link
err = client.NetworkRemove(context.Background(), netName)
assert.NilError(t, err)
assert.Check(t, n.IsNetworkNotAvailable(client, netName))
// verify the network delete did not delete the predefined link
n.LinkExists(t, master)
}
}
func testMacvlanSubinterface(client client.APIClient) func(*testing.T) {
return func(t *testing.T) {
// verify the same parent interface cannot be used if already in use by an existing network
master := "dm-dummy0"
parentName := "dm-dummy0.20"
n.CreateMasterDummy(t, master)
defer n.DeleteInterface(t, master)
n.CreateVlanInterface(t, master, parentName, "20")
netName := "dm-subinterface"
net.CreateNoError(t, context.Background(), client, netName,
net.WithMacvlan(parentName),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
// delete the network while preserving the parent link
err := client.NetworkRemove(context.Background(), netName)
assert.NilError(t, err)
assert.Check(t, n.IsNetworkNotAvailable(client, netName))
// verify the network delete did not delete the predefined link
n.LinkExists(t, parentName)
}
}
func testMacvlanNilParent(client client.APIClient) func(*testing.T) {
return func(t *testing.T) {
// macvlan bridge mode - dummy parent interface is provisioned dynamically
netName := "dm-nil-parent"
net.CreateNoError(t, context.Background(), client, netName,
net.WithMacvlan(""),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
ctx := context.Background()
id1 := container.Run(t, ctx, client, container.WithNetworkMode(netName))
id2 := container.Run(t, ctx, client, container.WithNetworkMode(netName))
_, err := container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
assert.Check(t, err == nil)
}
}
func testMacvlanInternalMode(client client.APIClient) func(*testing.T) {
return func(t *testing.T) {
// macvlan bridge mode - dummy parent interface is provisioned dynamically
netName := "dm-internal"
net.CreateNoError(t, context.Background(), client, netName,
net.WithMacvlan(""),
net.WithInternal(),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
ctx := context.Background()
id1 := container.Run(t, ctx, client, container.WithNetworkMode(netName))
id2 := container.Run(t, ctx, client, container.WithNetworkMode(netName))
timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
_, err := container.Exec(timeoutCtx, client, id1, []string{"ping", "-c", "1", "-w", "1", "8.8.8.8"})
// FIXME(vdemeester) check the time of error ?
assert.Check(t, err != nil)
assert.Check(t, timeoutCtx.Err() == context.DeadlineExceeded)
_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", id1})
assert.Check(t, err == nil)
}
}
func testMacvlanMultiSubnet(client client.APIClient) func(*testing.T) {
return func(t *testing.T) {
netName := "dualstackbridge"
net.CreateNoError(t, context.Background(), client, netName,
net.WithMacvlan(""),
net.WithIPv6(),
net.WithIPAM("172.28.100.0/24", ""),
net.WithIPAM("172.28.102.0/24", "172.28.102.254"),
net.WithIPAM("2001:db8:abc2::/64", ""),
net.WithIPAM("2001:db8:abc4::/64", "2001:db8:abc4::254"),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.100.0/24 and 2001:db8:abc2::/64
ctx := context.Background()
id1 := container.Run(t, ctx, client,
container.WithNetworkMode("dualstackbridge"),
container.WithIPv4("dualstackbridge", "172.28.100.20"),
container.WithIPv6("dualstackbridge", "2001:db8:abc2::20"),
)
id2 := container.Run(t, ctx, client,
container.WithNetworkMode("dualstackbridge"),
container.WithIPv4("dualstackbridge", "172.28.100.21"),
container.WithIPv6("dualstackbridge", "2001:db8:abc2::21"),
)
c1, err := client.ContainerInspect(ctx, id1)
assert.NilError(t, err)
// verify ipv4 connectivity to the explicit --ipv address second to first
_, err = container.Exec(ctx, client, id2, []string{"ping", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].IPAddress})
assert.NilError(t, err)
// verify ipv6 connectivity to the explicit --ipv6 address second to first
_, err = container.Exec(ctx, client, id2, []string{"ping6", "-c", "1", c1.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address})
assert.NilError(t, err)
// start dual stack containers and verify the user specified --ip and --ip6 addresses on subnets 172.28.102.0/24 and 2001:db8:abc4::/64
id3 := container.Run(t, ctx, client,
container.WithNetworkMode("dualstackbridge"),
container.WithIPv4("dualstackbridge", "172.28.102.20"),
container.WithIPv6("dualstackbridge", "2001:db8:abc4::20"),
)
id4 := container.Run(t, ctx, client,
container.WithNetworkMode("dualstackbridge"),
container.WithIPv4("dualstackbridge", "172.28.102.21"),
container.WithIPv6("dualstackbridge", "2001:db8:abc4::21"),
)
c3, err := client.ContainerInspect(ctx, id3)
assert.NilError(t, err)
// verify ipv4 connectivity to the explicit --ipv address from third to fourth
_, err = container.Exec(ctx, client, id4, []string{"ping", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].IPAddress})
assert.NilError(t, err)
// verify ipv6 connectivity to the explicit --ipv6 address from third to fourth
_, err = container.Exec(ctx, client, id4, []string{"ping6", "-c", "1", c3.NetworkSettings.Networks["dualstackbridge"].GlobalIPv6Address})
assert.NilError(t, err)
// Inspect the v4 gateway to ensure the proper default GW was assigned
assert.Equal(t, c1.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.100.1")
// Inspect the v6 gateway to ensure the proper default GW was assigned
assert.Equal(t, c1.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8:abc2::1")
// Inspect the v4 gateway to ensure the proper explicitly assigned default GW was assigned
assert.Equal(t, c3.NetworkSettings.Networks["dualstackbridge"].Gateway, "172.28.102.254")
// Inspect the v6 gateway to ensure the proper explicitly assigned default GW was assigned
assert.Equal(t, c3.NetworkSettings.Networks["dualstackbridge"].IPv6Gateway, "2001:db8.abc4::254")
}
}
func testMacvlanAddressing(client client.APIClient) func(*testing.T) {
return func(t *testing.T) {
// Ensure the default gateways, next-hops and default dev devices are properly set
netName := "dualstackbridge"
net.CreateNoError(t, context.Background(), client, netName,
net.WithMacvlan(""),
net.WithIPv6(),
net.WithOption("macvlan_mode", "bridge"),
net.WithIPAM("172.28.130.0/24", ""),
net.WithIPAM("2001:db8:abca::/64", "2001:db8:abca::254"),
)
assert.Check(t, n.IsNetworkAvailable(client, netName))
ctx := context.Background()
id1 := container.Run(t, ctx, client,
container.WithNetworkMode("dualstackbridge"),
)
// Validate macvlan bridge mode defaults gateway sets the default IPAM next-hop inferred from the subnet
result, err := container.Exec(ctx, client, id1, []string{"ip", "route"})
assert.NilError(t, err)
assert.Check(t, strings.Contains(result.Combined(), "default via 172.28.130.1 dev eth0"))
// Validate macvlan bridge mode sets the v6 gateway to the user specified default gateway/next-hop
result, err = container.Exec(ctx, client, id1, []string{"ip", "-6", "route"})
assert.NilError(t, err)
assert.Check(t, strings.Contains(result.Combined(), "default via 2001:db8:abca::254 dev eth0"))
}
}
// ensure Kernel version is >= v3.9 for macvlan support
func macvlanKernelSupport() bool {
return n.CheckKernelMajorVersionGreaterOrEqualThen(3, 9)
}

View file

@ -0,0 +1,33 @@
package macvlan // import "github.com/docker/docker/integration/network/macvlan"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,33 @@
package network // import "github.com/docker/docker/integration/network"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,58 @@
package network // import "github.com/docker/docker/integration/network"
import (
"bytes"
"context"
"os/exec"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestRunContainerWithBridgeNone(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run")
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, IsUserNamespace())
d := daemon.New(t)
d.StartWithBusybox(t, "-b", "none")
defer d.Stop(t)
client, err := d.NewClient()
assert.Check(t, err, "error creating client")
ctx := context.Background()
id1 := container.Run(t, ctx, client)
defer client.ContainerRemove(ctx, id1, types.ContainerRemoveOptions{Force: true})
result, err := container.Exec(ctx, client, id1, []string{"ip", "l"})
assert.NilError(t, err)
assert.Check(t, is.Equal(false, strings.Contains(result.Combined(), "eth0")), "There shouldn't be eth0 in container in default(bridge) mode when bridge network is disabled")
id2 := container.Run(t, ctx, client, container.WithNetworkMode("bridge"))
defer client.ContainerRemove(ctx, id2, types.ContainerRemoveOptions{Force: true})
result, err = container.Exec(ctx, client, id2, []string{"ip", "l"})
assert.NilError(t, err)
assert.Check(t, is.Equal(false, strings.Contains(result.Combined(), "eth0")), "There shouldn't be eth0 in container in bridge mode when bridge network is disabled")
nsCommand := "ls -l /proc/self/ns/net | awk -F '->' '{print $2}'"
cmd := exec.Command("sh", "-c", nsCommand)
stdout := bytes.NewBuffer(nil)
cmd.Stdout = stdout
err = cmd.Run()
assert.NilError(t, err, "Failed to get current process network namespace: %+v", err)
id3 := container.Run(t, ctx, client, container.WithNetworkMode("host"))
defer client.ContainerRemove(ctx, id3, types.ContainerRemoveOptions{Force: true})
result, err = container.Exec(ctx, client, id3, []string{"sh", "-c", nsCommand})
assert.NilError(t, err)
assert.Check(t, is.Equal(stdout.String(), result.Combined()), "The network namspace of container should be the same with host when --net=host and bridge network is disabled")
}

View file

@ -0,0 +1,362 @@
package network // import "github.com/docker/docker/integration/network"
import (
"context"
"testing"
"time"
"github.com/docker/docker/api/types"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/integration/internal/swarm"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
"gotest.tools/icmd"
"gotest.tools/poll"
"gotest.tools/skip"
)
// delInterface removes given network interface
func delInterface(t *testing.T, ifName string) {
icmd.RunCommand("ip", "link", "delete", ifName).Assert(t, icmd.Success)
icmd.RunCommand("iptables", "-t", "nat", "--flush").Assert(t, icmd.Success)
icmd.RunCommand("iptables", "--flush").Assert(t, icmd.Success)
}
func TestDaemonRestartWithLiveRestore(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "skip test from new feature")
d := daemon.New(t)
defer d.Stop(t)
d.Start(t)
d.Restart(t, "--live-restore=true",
"--default-address-pool", "base=175.30.0.0/16,size=16",
"--default-address-pool", "base=175.33.0.0/16,size=24")
// Verify bridge network's subnet
cli, err := d.NewClient()
assert.Assert(t, err)
defer cli.Close()
out, err := cli.NetworkInspect(context.Background(), "bridge", types.NetworkInspectOptions{})
assert.NilError(t, err)
// Make sure docker0 doesn't get override with new IP in live restore case
assert.Equal(t, out.IPAM.Config[0].Subnet, "172.18.0.0/16")
}
func TestDaemonDefaultNetworkPools(t *testing.T) {
// Remove docker0 bridge and the start daemon defining the predefined address pools
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "skip test from new feature")
defaultNetworkBridge := "docker0"
delInterface(t, defaultNetworkBridge)
d := daemon.New(t)
defer d.Stop(t)
d.Start(t,
"--default-address-pool", "base=175.30.0.0/16,size=16",
"--default-address-pool", "base=175.33.0.0/16,size=24")
// Verify bridge network's subnet
cli, err := d.NewClient()
assert.Assert(t, err)
defer cli.Close()
out, err := cli.NetworkInspect(context.Background(), "bridge", types.NetworkInspectOptions{})
assert.NilError(t, err)
assert.Equal(t, out.IPAM.Config[0].Subnet, "175.30.0.0/16")
// Create a bridge network and verify its subnet is the second default pool
name := "elango" + t.Name()
network.CreateNoError(t, context.Background(), cli, name,
network.WithDriver("bridge"),
)
out, err = cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{})
assert.NilError(t, err)
assert.Equal(t, out.IPAM.Config[0].Subnet, "175.33.0.0/24")
// Create a bridge network and verify its subnet is the third default pool
name = "saanvi" + t.Name()
network.CreateNoError(t, context.Background(), cli, name,
network.WithDriver("bridge"),
)
out, err = cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{})
assert.NilError(t, err)
assert.Equal(t, out.IPAM.Config[0].Subnet, "175.33.1.0/24")
delInterface(t, defaultNetworkBridge)
}
func TestDaemonRestartWithExistingNetwork(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "skip test from new feature")
defaultNetworkBridge := "docker0"
d := daemon.New(t)
d.Start(t)
defer d.Stop(t)
// Verify bridge network's subnet
cli, err := d.NewClient()
assert.Assert(t, err)
defer cli.Close()
// Create a bridge network
name := "elango" + t.Name()
network.CreateNoError(t, context.Background(), cli, name,
network.WithDriver("bridge"),
)
out, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{})
assert.NilError(t, err)
networkip := out.IPAM.Config[0].Subnet
// Restart daemon with default address pool option
d.Restart(t,
"--default-address-pool", "base=175.30.0.0/16,size=16",
"--default-address-pool", "base=175.33.0.0/16,size=24")
out1, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{})
assert.NilError(t, err)
assert.Equal(t, out1.IPAM.Config[0].Subnet, networkip)
delInterface(t, defaultNetworkBridge)
}
func TestDaemonRestartWithExistingNetworkWithDefaultPoolRange(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "skip test from new feature")
defaultNetworkBridge := "docker0"
d := daemon.New(t)
d.Start(t)
defer d.Stop(t)
// Verify bridge network's subnet
cli, err := d.NewClient()
assert.Assert(t, err)
defer cli.Close()
// Create a bridge network
name := "elango" + t.Name()
network.CreateNoError(t, context.Background(), cli, name,
network.WithDriver("bridge"),
)
out, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{})
assert.NilError(t, err)
networkip := out.IPAM.Config[0].Subnet
// Create a bridge network
name = "sthira" + t.Name()
network.CreateNoError(t, context.Background(), cli, name,
network.WithDriver("bridge"),
)
out, err = cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{})
assert.NilError(t, err)
networkip2 := out.IPAM.Config[0].Subnet
// Restart daemon with default address pool option
d.Restart(t,
"--default-address-pool", "base=175.18.0.0/16,size=16",
"--default-address-pool", "base=175.19.0.0/16,size=24")
// Create a bridge network
name = "saanvi" + t.Name()
network.CreateNoError(t, context.Background(), cli, name,
network.WithDriver("bridge"),
)
out1, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{})
assert.NilError(t, err)
assert.Check(t, out1.IPAM.Config[0].Subnet != networkip)
assert.Check(t, out1.IPAM.Config[0].Subnet != networkip2)
delInterface(t, defaultNetworkBridge)
}
func TestDaemonWithBipAndDefaultNetworkPool(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.38"), "skip test from new feature")
defaultNetworkBridge := "docker0"
d := daemon.New(t)
defer d.Stop(t)
d.Start(t, "--bip=172.60.0.1/16",
"--default-address-pool", "base=175.30.0.0/16,size=16",
"--default-address-pool", "base=175.33.0.0/16,size=24")
// Verify bridge network's subnet
cli, err := d.NewClient()
assert.Assert(t, err)
defer cli.Close()
out, err := cli.NetworkInspect(context.Background(), "bridge", types.NetworkInspectOptions{})
assert.NilError(t, err)
// Make sure BIP IP doesn't get override with new default address pool .
assert.Equal(t, out.IPAM.Config[0].Subnet, "172.60.0.1/16")
delInterface(t, defaultNetworkBridge)
}
func TestServiceWithPredefinedNetwork(t *testing.T) {
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
hostName := "host"
var instances uint64 = 1
serviceName := "TestService" + t.Name()
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(serviceName),
swarm.ServiceWithNetwork(hostName),
)
poll.WaitOn(t, serviceRunningCount(client, serviceID, instances), swarm.ServicePoll)
_, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
assert.NilError(t, err)
err = client.ServiceRemove(context.Background(), serviceID)
assert.NilError(t, err)
}
const ingressNet = "ingress"
func TestServiceRemoveKeepsIngressNetwork(t *testing.T) {
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
poll.WaitOn(t, swarmIngressReady(client), swarm.NetworkPoll)
var instances uint64 = 1
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(t.Name()+"-service"),
swarm.ServiceWithEndpoint(&swarmtypes.EndpointSpec{
Ports: []swarmtypes.PortConfig{
{
Protocol: swarmtypes.PortConfigProtocolTCP,
TargetPort: 80,
PublishMode: swarmtypes.PortConfigPublishModeIngress,
},
},
}),
)
poll.WaitOn(t, serviceRunningCount(client, serviceID, instances), swarm.ServicePoll)
_, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
assert.NilError(t, err)
err = client.ServiceRemove(context.Background(), serviceID)
assert.NilError(t, err)
poll.WaitOn(t, serviceIsRemoved(client, serviceID), swarm.ServicePoll)
poll.WaitOn(t, noServices(client), swarm.ServicePoll)
// Ensure that "ingress" is not removed or corrupted
time.Sleep(10 * time.Second)
netInfo, err := client.NetworkInspect(context.Background(), ingressNet, types.NetworkInspectOptions{
Verbose: true,
Scope: "swarm",
})
assert.NilError(t, err, "Ingress network was removed after removing service!")
assert.Assert(t, len(netInfo.Containers) != 0, "No load balancing endpoints in ingress network")
assert.Assert(t, len(netInfo.Peers) != 0, "No peers (including self) in ingress network")
_, ok := netInfo.Containers["ingress-sbox"]
assert.Assert(t, ok, "ingress-sbox not present in ingress network")
}
func serviceRunningCount(client client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
services, err := client.ServiceList(context.Background(), types.ServiceListOptions{})
if err != nil {
return poll.Error(err)
}
if len(services) != int(instances) {
return poll.Continue("Service count at %d waiting for %d", len(services), instances)
}
return poll.Success()
}
}
func swarmIngressReady(client client.NetworkAPIClient) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
netInfo, err := client.NetworkInspect(context.Background(), ingressNet, types.NetworkInspectOptions{
Verbose: true,
Scope: "swarm",
})
if err != nil {
return poll.Error(err)
}
np := len(netInfo.Peers)
nc := len(netInfo.Containers)
if np == 0 || nc == 0 {
return poll.Continue("ingress not ready: %d peers and %d containers", nc, np)
}
_, ok := netInfo.Containers["ingress-sbox"]
if !ok {
return poll.Continue("ingress not ready: does not contain the ingress-sbox")
}
return poll.Success()
}
}
func noServices(client client.ServiceAPIClient) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
services, err := client.ServiceList(context.Background(), types.ServiceListOptions{})
switch {
case err != nil:
return poll.Error(err)
case len(services) == 0:
return poll.Success()
default:
return poll.Continue("Service count at %d waiting for 0", len(services))
}
}
}
func TestServiceWithDefaultAddressPoolInit(t *testing.T) {
defer setupTest(t)()
var ops = []func(*daemon.Daemon){}
ipAddr := []string{"20.20.0.0/16"}
ops = append(ops, daemon.WithSwarmDefaultAddrPool(ipAddr))
ops = append(ops, daemon.WithSwarmDefaultAddrPoolSubnetSize(24))
d := swarm.NewSwarm(t, testEnv, ops...)
cli := d.NewClientT(t)
defer cli.Close()
// Create a overlay network
name := "saanvisthira" + t.Name()
network.CreateNoError(t, context.Background(), cli, name,
network.WithDriver("overlay"))
var instances uint64 = 1
serviceName := "TestService" + t.Name()
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(serviceName),
swarm.ServiceWithNetwork(name),
)
poll.WaitOn(t, serviceRunningCount(cli, serviceID, instances), swarm.ServicePoll)
_, _, err := cli.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
assert.NilError(t, err)
out, err := cli.NetworkInspect(context.Background(), name, types.NetworkInspectOptions{})
assert.NilError(t, err)
assert.Equal(t, out.IPAM.Config[0].Subnet, "20.20.0.0/24")
err = cli.ServiceRemove(context.Background(), serviceID)
assert.NilError(t, err)
d.SwarmLeave(true)
d.Stop(t)
// Clean up , set it back to original one to make sure other tests don't fail
ipAddr = []string{"10.10.0.0/8"}
ops = append(ops, daemon.WithSwarmDefaultAddrPool(ipAddr))
ops = append(ops, daemon.WithSwarmDefaultAddrPoolSubnetSize(24))
d = swarm.NewSwarm(t, testEnv, ops...)
d.SwarmLeave(true)
defer d.Stop(t)
}

View file

@ -0,0 +1,521 @@
// +build !windows
package authz // import "github.com/docker/docker/integration/plugin/authz"
import (
"context"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/environment"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/authorization"
"gotest.tools/assert"
"gotest.tools/skip"
)
const (
testAuthZPlugin = "authzplugin"
unauthorizedMessage = "User unauthorized authz plugin"
errorMessage = "something went wrong..."
serverVersionAPI = "/version"
)
var (
alwaysAllowed = []string{"/_ping", "/info"}
ctrl *authorizationController
)
type authorizationController struct {
reqRes authorization.Response // reqRes holds the plugin response to the initial client request
resRes authorization.Response // resRes holds the plugin response to the daemon response
versionReqCount int // versionReqCount counts the number of requests to the server version API endpoint
versionResCount int // versionResCount counts the number of responses from the server version API endpoint
requestsURIs []string // requestsURIs stores all request URIs that are sent to the authorization controller
reqUser string
resUser string
}
func setupTestV1(t *testing.T) func() {
ctrl = &authorizationController{}
teardown := setupTest(t)
err := os.MkdirAll("/etc/docker/plugins", 0755)
assert.NilError(t, err)
fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
err = ioutil.WriteFile(fileName, []byte(server.URL), 0644)
assert.NilError(t, err)
return func() {
err := os.RemoveAll("/etc/docker/plugins")
assert.NilError(t, err)
teardown()
ctrl = nil
}
}
// check for always allowed endpoints to not inhibit test framework functions
func isAllowed(reqURI string) bool {
for _, endpoint := range alwaysAllowed {
if strings.HasSuffix(reqURI, endpoint) {
return true
}
}
return false
}
func TestAuthZPluginAllowRequest(t *testing.T) {
defer setupTestV1(t)()
ctrl.reqRes.Allow = true
ctrl.resRes.Allow = true
d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
client, err := d.NewClient()
assert.NilError(t, err)
ctx := context.Background()
// Ensure command successful
cID := container.Run(t, ctx, client)
assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", cID))
_, err = client.ServerVersion(ctx)
assert.NilError(t, err)
assert.Equal(t, 1, ctrl.versionReqCount)
assert.Equal(t, 1, ctrl.versionResCount)
}
func TestAuthZPluginTLS(t *testing.T) {
defer setupTestV1(t)()
const (
testDaemonHTTPSAddr = "tcp://localhost:4271"
cacertPath = "../../testdata/https/ca.pem"
serverCertPath = "../../testdata/https/server-cert.pem"
serverKeyPath = "../../testdata/https/server-key.pem"
clientCertPath = "../../testdata/https/client-cert.pem"
clientKeyPath = "../../testdata/https/client-key.pem"
)
d.Start(t,
"--authorization-plugin="+testAuthZPlugin,
"--tlsverify",
"--tlscacert", cacertPath,
"--tlscert", serverCertPath,
"--tlskey", serverKeyPath,
"-H", testDaemonHTTPSAddr)
ctrl.reqRes.Allow = true
ctrl.resRes.Allow = true
client, err := newTLSAPIClient(testDaemonHTTPSAddr, cacertPath, clientCertPath, clientKeyPath)
assert.NilError(t, err)
_, err = client.ServerVersion(context.Background())
assert.NilError(t, err)
assert.Equal(t, "client", ctrl.reqUser)
assert.Equal(t, "client", ctrl.resUser)
}
func newTLSAPIClient(host, cacertPath, certPath, keyPath string) (client.APIClient, error) {
dialer := &net.Dialer{
KeepAlive: 30 * time.Second,
Timeout: 30 * time.Second,
}
return client.NewClientWithOpts(
client.WithTLSClientConfig(cacertPath, certPath, keyPath),
client.WithDialer(dialer),
client.WithHost(host))
}
func TestAuthZPluginDenyRequest(t *testing.T) {
defer setupTestV1(t)()
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
ctrl.reqRes.Allow = false
ctrl.reqRes.Msg = unauthorizedMessage
client, err := d.NewClient()
assert.NilError(t, err)
// Ensure command is blocked
_, err = client.ServerVersion(context.Background())
assert.Assert(t, err != nil)
assert.Equal(t, 1, ctrl.versionReqCount)
assert.Equal(t, 0, ctrl.versionResCount)
// Ensure unauthorized message appears in response
assert.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
}
// TestAuthZPluginAPIDenyResponse validates that when authorization
// plugin deny the request, the status code is forbidden
func TestAuthZPluginAPIDenyResponse(t *testing.T) {
defer setupTestV1(t)()
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
ctrl.reqRes.Allow = false
ctrl.resRes.Msg = unauthorizedMessage
daemonURL, err := url.Parse(d.Sock())
assert.NilError(t, err)
conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
assert.NilError(t, err)
client := httputil.NewClientConn(conn, nil)
req, err := http.NewRequest("GET", "/version", nil)
assert.NilError(t, err)
resp, err := client.Do(req)
assert.NilError(t, err)
assert.DeepEqual(t, http.StatusForbidden, resp.StatusCode)
}
func TestAuthZPluginDenyResponse(t *testing.T) {
defer setupTestV1(t)()
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
ctrl.reqRes.Allow = true
ctrl.resRes.Allow = false
ctrl.resRes.Msg = unauthorizedMessage
client, err := d.NewClient()
assert.NilError(t, err)
// Ensure command is blocked
_, err = client.ServerVersion(context.Background())
assert.Assert(t, err != nil)
assert.Equal(t, 1, ctrl.versionReqCount)
assert.Equal(t, 1, ctrl.versionResCount)
// Ensure unauthorized message appears in response
assert.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
}
// TestAuthZPluginAllowEventStream verifies event stream propagates
// correctly after request pass through by the authorization plugin
func TestAuthZPluginAllowEventStream(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTestV1(t)()
ctrl.reqRes.Allow = true
ctrl.resRes.Allow = true
d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
client, err := d.NewClient()
assert.NilError(t, err)
ctx := context.Background()
startTime := strconv.FormatInt(systemTime(t, client, testEnv).Unix(), 10)
events, errs, cancel := systemEventsSince(client, startTime)
defer cancel()
// Create a container and wait for the creation events
cID := container.Run(t, ctx, client)
for i := 0; i < 100; i++ {
c, err := client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
if c.State.Running {
break
}
if i == 99 {
t.Fatal("Container didn't run within 10s")
}
time.Sleep(100 * time.Millisecond)
}
created := false
started := false
for !created && !started {
select {
case event := <-events:
if event.Type == eventtypes.ContainerEventType && event.Actor.ID == cID {
if event.Action == "create" {
created = true
}
if event.Action == "start" {
started = true
}
}
case err := <-errs:
if err == io.EOF {
t.Fatal("premature end of event stream")
}
assert.NilError(t, err)
case <-time.After(30 * time.Second):
// Fail the test
t.Fatal("event stream timeout")
}
}
// Ensure both events and container endpoints are passed to the
// authorization plugin
assertURIRecorded(t, ctrl.requestsURIs, "/events")
assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", cID))
}
func systemTime(t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time {
if testEnv.IsLocalDaemon() {
return time.Now()
}
ctx := context.Background()
info, err := client.Info(ctx)
assert.NilError(t, err)
dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
assert.NilError(t, err, "invalid time format in GET /info response")
return dt
}
func systemEventsSince(client client.APIClient, since string) (<-chan eventtypes.Message, <-chan error, func()) {
eventOptions := types.EventsOptions{
Since: since,
}
ctx, cancel := context.WithCancel(context.Background())
events, errs := client.Events(ctx, eventOptions)
return events, errs, cancel
}
func TestAuthZPluginErrorResponse(t *testing.T) {
defer setupTestV1(t)()
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
ctrl.reqRes.Allow = true
ctrl.resRes.Err = errorMessage
client, err := d.NewClient()
assert.NilError(t, err)
// Ensure command is blocked
_, err = client.ServerVersion(context.Background())
assert.Assert(t, err != nil)
assert.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage), err.Error())
}
func TestAuthZPluginErrorRequest(t *testing.T) {
defer setupTestV1(t)()
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
ctrl.reqRes.Err = errorMessage
client, err := d.NewClient()
assert.NilError(t, err)
// Ensure command is blocked
_, err = client.ServerVersion(context.Background())
assert.Assert(t, err != nil)
assert.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage), err.Error())
}
func TestAuthZPluginEnsureNoDuplicatePluginRegistration(t *testing.T) {
defer setupTestV1(t)()
d.Start(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
ctrl.reqRes.Allow = true
ctrl.resRes.Allow = true
client, err := d.NewClient()
assert.NilError(t, err)
_, err = client.ServerVersion(context.Background())
assert.NilError(t, err)
// assert plugin is only called once..
assert.Equal(t, 1, ctrl.versionReqCount)
assert.Equal(t, 1, ctrl.versionResCount)
}
func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) {
defer setupTestV1(t)()
ctrl.reqRes.Allow = true
ctrl.resRes.Allow = true
d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
client, err := d.NewClient()
assert.NilError(t, err)
ctx := context.Background()
tmp, err := ioutil.TempDir("", "test-authz-load-import")
assert.NilError(t, err)
defer os.RemoveAll(tmp)
savedImagePath := filepath.Join(tmp, "save.tar")
err = imageSave(client, savedImagePath, "busybox")
assert.NilError(t, err)
err = imageLoad(client, savedImagePath)
assert.NilError(t, err)
exportedImagePath := filepath.Join(tmp, "export.tar")
cID := container.Run(t, ctx, client)
responseReader, err := client.ContainerExport(context.Background(), cID)
assert.NilError(t, err)
defer responseReader.Close()
file, err := os.Create(exportedImagePath)
assert.NilError(t, err)
defer file.Close()
_, err = io.Copy(file, responseReader)
assert.NilError(t, err)
err = imageImport(client, exportedImagePath)
assert.NilError(t, err)
}
func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) {
defer setupTestV1(t)()
ctrl.reqRes.Allow = true
ctrl.resRes.Allow = true
d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
dir, err := ioutil.TempDir("", t.Name())
assert.Assert(t, err)
defer os.RemoveAll(dir)
f, err := ioutil.TempFile(dir, "send")
assert.Assert(t, err)
defer f.Close()
buf := make([]byte, 1024)
fileSize := len(buf) * 1024 * 10
for written := 0; written < fileSize; {
n, err := f.Write(buf)
assert.Assert(t, err)
written += n
}
ctx := context.Background()
client, err := d.NewClient()
assert.Assert(t, err)
cID := container.Run(t, ctx, client)
defer client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true})
_, err = f.Seek(0, io.SeekStart)
assert.Assert(t, err)
srcInfo, err := archive.CopyInfoSourcePath(f.Name(), false)
assert.Assert(t, err)
srcArchive, err := archive.TarResource(srcInfo)
assert.Assert(t, err)
defer srcArchive.Close()
dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, archive.CopyInfo{Path: "/test"})
assert.Assert(t, err)
err = client.CopyToContainer(ctx, cID, dstDir, preparedArchive, types.CopyToContainerOptions{})
assert.Assert(t, err)
rdr, _, err := client.CopyFromContainer(ctx, cID, "/test")
assert.Assert(t, err)
_, err = io.Copy(ioutil.Discard, rdr)
assert.Assert(t, err)
}
func imageSave(client client.APIClient, path, image string) error {
ctx := context.Background()
responseReader, err := client.ImageSave(ctx, []string{image})
if err != nil {
return err
}
defer responseReader.Close()
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, responseReader)
return err
}
func imageLoad(client client.APIClient, path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
quiet := true
ctx := context.Background()
response, err := client.ImageLoad(ctx, file, quiet)
if err != nil {
return err
}
defer response.Body.Close()
return nil
}
func imageImport(client client.APIClient, path string) error {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
options := types.ImageImportOptions{}
ref := ""
source := types.ImageImportSource{
Source: file,
SourceName: "-",
}
ctx := context.Background()
responseReader, err := client.ImageImport(ctx, source, ref, options)
if err != nil {
return err
}
defer responseReader.Close()
return nil
}
func TestAuthZPluginHeader(t *testing.T) {
defer setupTestV1(t)()
ctrl.reqRes.Allow = true
ctrl.resRes.Allow = true
d.StartWithBusybox(t, "--debug", "--authorization-plugin="+testAuthZPlugin)
daemonURL, err := url.Parse(d.Sock())
assert.NilError(t, err)
conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
assert.NilError(t, err)
client := httputil.NewClientConn(conn, nil)
req, err := http.NewRequest("GET", "/version", nil)
assert.NilError(t, err)
resp, err := client.Do(req)
assert.NilError(t, err)
assert.Equal(t, "application/json", resp.Header["Content-Type"][0])
}
// assertURIRecorded verifies that the given URI was sent and recorded
// in the authz plugin
func assertURIRecorded(t *testing.T, uris []string, uri string) {
var found bool
for _, u := range uris {
if strings.Contains(u, uri) {
found = true
break
}
}
if !found {
t.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
}
}

View file

@ -0,0 +1,175 @@
// +build !windows
package authz // import "github.com/docker/docker/integration/plugin/authz"
import (
"context"
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/requirement"
"gotest.tools/assert"
"gotest.tools/skip"
)
var (
authzPluginName = "riyaz/authz-no-volume-plugin"
authzPluginTag = "latest"
authzPluginNameWithTag = authzPluginName + ":" + authzPluginTag
authzPluginBadManifestName = "riyaz/authz-plugin-bad-manifest"
nonexistentAuthzPluginName = "riyaz/nonexistent-authz-plugin"
)
func setupTestV2(t *testing.T) func() {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
skip.If(t, !requirement.HasHubConnectivity(t))
teardown := setupTest(t)
d.Start(t)
return teardown
}
func TestAuthZPluginV2AllowNonVolumeRequest(t *testing.T) {
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
defer setupTestV2(t)()
client, err := d.NewClient()
assert.NilError(t, err)
ctx := context.Background()
// Install authz plugin
err = pluginInstallGrantAllPermissions(client, authzPluginNameWithTag)
assert.NilError(t, err)
// start the daemon with the plugin and load busybox, --net=none build fails otherwise
// because it needs to pull busybox
d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
d.LoadBusybox(t)
// Ensure docker run command and accompanying docker ps are successful
cID := container.Run(t, ctx, client)
_, err = client.ContainerInspect(ctx, cID)
assert.NilError(t, err)
}
func TestAuthZPluginV2Disable(t *testing.T) {
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
defer setupTestV2(t)()
client, err := d.NewClient()
assert.NilError(t, err)
// Install authz plugin
err = pluginInstallGrantAllPermissions(client, authzPluginNameWithTag)
assert.NilError(t, err)
d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
d.LoadBusybox(t)
_, err = client.VolumeCreate(context.Background(), volumetypes.VolumeCreateBody{Driver: "local"})
assert.Assert(t, err != nil)
assert.Assert(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
// disable the plugin
err = client.PluginDisable(context.Background(), authzPluginNameWithTag, types.PluginDisableOptions{})
assert.NilError(t, err)
// now test to see if the docker api works.
_, err = client.VolumeCreate(context.Background(), volumetypes.VolumeCreateBody{Driver: "local"})
assert.NilError(t, err)
}
func TestAuthZPluginV2RejectVolumeRequests(t *testing.T) {
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
defer setupTestV2(t)()
client, err := d.NewClient()
assert.NilError(t, err)
// Install authz plugin
err = pluginInstallGrantAllPermissions(client, authzPluginNameWithTag)
assert.NilError(t, err)
// restart the daemon with the plugin
d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
_, err = client.VolumeCreate(context.Background(), volumetypes.VolumeCreateBody{Driver: "local"})
assert.Assert(t, err != nil)
assert.Assert(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
_, err = client.VolumeList(context.Background(), filters.Args{})
assert.Assert(t, err != nil)
assert.Assert(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
// The plugin will block the command before it can determine the volume does not exist
err = client.VolumeRemove(context.Background(), "test", false)
assert.Assert(t, err != nil)
assert.Assert(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
_, err = client.VolumeInspect(context.Background(), "test")
assert.Assert(t, err != nil)
assert.Assert(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
_, err = client.VolumesPrune(context.Background(), filters.Args{})
assert.Assert(t, err != nil)
assert.Assert(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
}
func TestAuthZPluginV2BadManifestFailsDaemonStart(t *testing.T) {
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
defer setupTestV2(t)()
client, err := d.NewClient()
assert.NilError(t, err)
// Install authz plugin with bad manifest
err = pluginInstallGrantAllPermissions(client, authzPluginBadManifestName)
assert.NilError(t, err)
// start the daemon with the plugin, it will error
err = d.RestartWithError("--authorization-plugin=" + authzPluginBadManifestName)
assert.Assert(t, err != nil)
// restarting the daemon without requiring the plugin will succeed
d.Start(t)
}
func TestAuthZPluginV2NonexistentFailsDaemonStart(t *testing.T) {
defer setupTestV2(t)()
// start the daemon with a non-existent authz plugin, it will error
err := d.RestartWithError("--authorization-plugin=" + nonexistentAuthzPluginName)
assert.Assert(t, err != nil)
// restarting the daemon without requiring the plugin will succeed
d.Start(t)
}
func pluginInstallGrantAllPermissions(client client.APIClient, name string) error {
ctx := context.Background()
options := types.PluginInstallOptions{
RemoteRef: name,
AcceptAllPermissions: true,
}
responseReader, err := client.PluginInstall(ctx, "", options)
if err != nil {
return err
}
defer responseReader.Close()
// we have to read the response out here because the client API
// actually starts a goroutine which we can only be sure has
// completed when we get EOF from reading responseBody
_, err = ioutil.ReadAll(responseReader)
return err
}

View file

@ -0,0 +1,180 @@
// +build !windows
package authz // import "github.com/docker/docker/integration/plugin/authz"
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/internal/test/environment"
"github.com/docker/docker/pkg/authorization"
"github.com/docker/docker/pkg/plugins"
"gotest.tools/skip"
)
var (
testEnv *environment.Execution
d *daemon.Daemon
server *httptest.Server
)
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
setupSuite()
exitCode := m.Run()
teardownSuite()
os.Exit(exitCode)
}
func setupTest(t *testing.T) func() {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
environment.ProtectAll(t, testEnv)
d = daemon.New(t, daemon.WithExperimental)
return func() {
if d != nil {
d.Stop(t)
}
testEnv.Clean(t)
}
}
func setupSuite() {
mux := http.NewServeMux()
server = httptest.NewServer(mux)
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}})
if err != nil {
panic("could not marshal json for /Plugin.Activate: " + err.Error())
}
w.Write(b)
})
mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic("could not read body for /AuthZPlugin.AuthZReq: " + err.Error())
}
authReq := authorization.Request{}
err = json.Unmarshal(body, &authReq)
if err != nil {
panic("could not unmarshal json for /AuthZPlugin.AuthZReq: " + err.Error())
}
assertBody(authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody)
assertAuthHeaders(authReq.RequestHeaders)
// Count only server version api
if strings.HasSuffix(authReq.RequestURI, serverVersionAPI) {
ctrl.versionReqCount++
}
ctrl.requestsURIs = append(ctrl.requestsURIs, authReq.RequestURI)
reqRes := ctrl.reqRes
if isAllowed(authReq.RequestURI) {
reqRes = authorization.Response{Allow: true}
}
if reqRes.Err != "" {
w.WriteHeader(http.StatusInternalServerError)
}
b, err := json.Marshal(reqRes)
if err != nil {
panic("could not marshal json for /AuthZPlugin.AuthZReq: " + err.Error())
}
ctrl.reqUser = authReq.User
w.Write(b)
})
mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
panic("could not read body for /AuthZPlugin.AuthZRes: " + err.Error())
}
authReq := authorization.Request{}
err = json.Unmarshal(body, &authReq)
if err != nil {
panic("could not unmarshal json for /AuthZPlugin.AuthZRes: " + err.Error())
}
assertBody(authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody)
assertAuthHeaders(authReq.ResponseHeaders)
// Count only server version api
if strings.HasSuffix(authReq.RequestURI, serverVersionAPI) {
ctrl.versionResCount++
}
resRes := ctrl.resRes
if isAllowed(authReq.RequestURI) {
resRes = authorization.Response{Allow: true}
}
if resRes.Err != "" {
w.WriteHeader(http.StatusInternalServerError)
}
b, err := json.Marshal(resRes)
if err != nil {
panic("could not marshal json for /AuthZPlugin.AuthZRes: " + err.Error())
}
ctrl.resUser = authReq.User
w.Write(b)
})
}
func teardownSuite() {
if server == nil {
return
}
server.Close()
}
// assertAuthHeaders validates authentication headers are removed
func assertAuthHeaders(headers map[string]string) error {
for k := range headers {
if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") {
panic(fmt.Sprintf("Found authentication headers in request '%v'", headers))
}
}
return nil
}
// assertBody asserts that body is removed for non text/json requests
func assertBody(requestURI string, headers map[string]string, body []byte) {
if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 {
panic("Body included for authentication endpoint " + string(body))
}
for k, v := range headers {
if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" {
return
}
}
if len(body) > 0 {
panic(fmt.Sprintf("Body included while it should not (Headers: '%v')", headers))
}
}

View file

@ -0,0 +1,463 @@
package graphdriver
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"runtime"
"testing"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/daemon/graphdriver"
"github.com/docker/docker/daemon/graphdriver/vfs"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/requirement"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/plugins"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
type graphEventsCounter struct {
activations int
creations int
removals int
gets int
puts int
stats int
cleanups int
exists int
init int
metadata int
diff int
applydiff int
changes int
diffsize int
}
func TestExternalGraphDriver(t *testing.T) {
skip.If(t, runtime.GOOS == "windows")
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
skip.If(t, !requirement.HasHubConnectivity(t))
// Setup plugin(s)
ec := make(map[string]*graphEventsCounter)
sserver := setupPluginViaSpecFile(t, ec)
jserver := setupPluginViaJSONFile(t, ec)
// Create daemon
d := daemon.New(t, daemon.WithExperimental)
c := d.NewClientT(t)
for _, tc := range []struct {
name string
test func(client.APIClient, *daemon.Daemon) func(*testing.T)
}{
{
name: "json",
test: testExternalGraphDriver("json", ec),
},
{
name: "spec",
test: testExternalGraphDriver("spec", ec),
},
{
name: "pull",
test: testGraphDriverPull,
},
} {
t.Run(tc.name, tc.test(c, d))
}
sserver.Close()
jserver.Close()
err := os.RemoveAll("/etc/docker/plugins")
assert.NilError(t, err)
}
func setupPluginViaSpecFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
setupPlugin(t, ec, "spec", mux, []byte(server.URL))
return server
}
func setupPluginViaJSONFile(t *testing.T, ec map[string]*graphEventsCounter) *httptest.Server {
mux := http.NewServeMux()
server := httptest.NewServer(mux)
p := plugins.NewLocalPlugin("json-external-graph-driver", server.URL)
b, err := json.Marshal(p)
assert.NilError(t, err)
setupPlugin(t, ec, "json", mux, b)
return server
}
func setupPlugin(t *testing.T, ec map[string]*graphEventsCounter, ext string, mux *http.ServeMux, b []byte) {
name := fmt.Sprintf("%s-external-graph-driver", ext)
type graphDriverRequest struct {
ID string `json:",omitempty"`
Parent string `json:",omitempty"`
MountLabel string `json:",omitempty"`
ReadOnly bool `json:",omitempty"`
}
type graphDriverResponse struct {
Err error `json:",omitempty"`
Dir string `json:",omitempty"`
Exists bool `json:",omitempty"`
Status [][2]string `json:",omitempty"`
Metadata map[string]string `json:",omitempty"`
Changes []archive.Change `json:",omitempty"`
Size int64 `json:",omitempty"`
}
respond := func(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
switch t := data.(type) {
case error:
fmt.Fprintln(w, fmt.Sprintf(`{"Err": %q}`, t.Error()))
case string:
fmt.Fprintln(w, t)
default:
json.NewEncoder(w).Encode(&data)
}
}
decReq := func(b io.ReadCloser, out interface{}, w http.ResponseWriter) error {
defer b.Close()
if err := json.NewDecoder(b).Decode(&out); err != nil {
http.Error(w, fmt.Sprintf("error decoding json: %s", err.Error()), 500)
}
return nil
}
base, err := ioutil.TempDir("", name)
assert.NilError(t, err)
vfsProto, err := vfs.Init(base, []string{}, nil, nil)
assert.NilError(t, err, "error initializing graph driver")
driver := graphdriver.NewNaiveDiffDriver(vfsProto, nil, nil)
ec[ext] = &graphEventsCounter{}
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
ec[ext].activations++
respond(w, `{"Implements": ["GraphDriver"]}`)
})
mux.HandleFunc("/GraphDriver.Init", func(w http.ResponseWriter, r *http.Request) {
ec[ext].init++
respond(w, "{}")
})
mux.HandleFunc("/GraphDriver.CreateReadWrite", func(w http.ResponseWriter, r *http.Request) {
ec[ext].creations++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
if err := driver.CreateReadWrite(req.ID, req.Parent, nil); err != nil {
respond(w, err)
return
}
respond(w, "{}")
})
mux.HandleFunc("/GraphDriver.Create", func(w http.ResponseWriter, r *http.Request) {
ec[ext].creations++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
if err := driver.Create(req.ID, req.Parent, nil); err != nil {
respond(w, err)
return
}
respond(w, "{}")
})
mux.HandleFunc("/GraphDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
ec[ext].removals++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
if err := driver.Remove(req.ID); err != nil {
respond(w, err)
return
}
respond(w, "{}")
})
mux.HandleFunc("/GraphDriver.Get", func(w http.ResponseWriter, r *http.Request) {
ec[ext].gets++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
// TODO @gupta-ak: Figure out what to do here.
dir, err := driver.Get(req.ID, req.MountLabel)
if err != nil {
respond(w, err)
return
}
respond(w, &graphDriverResponse{Dir: dir.Path()})
})
mux.HandleFunc("/GraphDriver.Put", func(w http.ResponseWriter, r *http.Request) {
ec[ext].puts++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
if err := driver.Put(req.ID); err != nil {
respond(w, err)
return
}
respond(w, "{}")
})
mux.HandleFunc("/GraphDriver.Exists", func(w http.ResponseWriter, r *http.Request) {
ec[ext].exists++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
respond(w, &graphDriverResponse{Exists: driver.Exists(req.ID)})
})
mux.HandleFunc("/GraphDriver.Status", func(w http.ResponseWriter, r *http.Request) {
ec[ext].stats++
respond(w, &graphDriverResponse{Status: driver.Status()})
})
mux.HandleFunc("/GraphDriver.Cleanup", func(w http.ResponseWriter, r *http.Request) {
ec[ext].cleanups++
err := driver.Cleanup()
if err != nil {
respond(w, err)
return
}
respond(w, `{}`)
})
mux.HandleFunc("/GraphDriver.GetMetadata", func(w http.ResponseWriter, r *http.Request) {
ec[ext].metadata++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
data, err := driver.GetMetadata(req.ID)
if err != nil {
respond(w, err)
return
}
respond(w, &graphDriverResponse{Metadata: data})
})
mux.HandleFunc("/GraphDriver.Diff", func(w http.ResponseWriter, r *http.Request) {
ec[ext].diff++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
diff, err := driver.Diff(req.ID, req.Parent)
if err != nil {
respond(w, err)
return
}
io.Copy(w, diff)
})
mux.HandleFunc("/GraphDriver.Changes", func(w http.ResponseWriter, r *http.Request) {
ec[ext].changes++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
changes, err := driver.Changes(req.ID, req.Parent)
if err != nil {
respond(w, err)
return
}
respond(w, &graphDriverResponse{Changes: changes})
})
mux.HandleFunc("/GraphDriver.ApplyDiff", func(w http.ResponseWriter, r *http.Request) {
ec[ext].applydiff++
diff := r.Body
defer r.Body.Close()
id := r.URL.Query().Get("id")
parent := r.URL.Query().Get("parent")
if id == "" {
http.Error(w, fmt.Sprintf("missing id"), 409)
}
size, err := driver.ApplyDiff(id, parent, diff)
if err != nil {
respond(w, err)
return
}
respond(w, &graphDriverResponse{Size: size})
})
mux.HandleFunc("/GraphDriver.DiffSize", func(w http.ResponseWriter, r *http.Request) {
ec[ext].diffsize++
var req graphDriverRequest
if err := decReq(r.Body, &req, w); err != nil {
return
}
size, err := driver.DiffSize(req.ID, req.Parent)
if err != nil {
respond(w, err)
return
}
respond(w, &graphDriverResponse{Size: size})
})
err = os.MkdirAll("/etc/docker/plugins", 0755)
assert.NilError(t, err)
specFile := "/etc/docker/plugins/" + name + "." + ext
err = ioutil.WriteFile(specFile, b, 0644)
assert.NilError(t, err)
}
func testExternalGraphDriver(ext string, ec map[string]*graphEventsCounter) func(client.APIClient, *daemon.Daemon) func(*testing.T) {
return func(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
return func(t *testing.T) {
driverName := fmt.Sprintf("%s-external-graph-driver", ext)
d.StartWithBusybox(t, "-s", driverName)
ctx := context.Background()
testGraphDriver(t, c, ctx, driverName, func(t *testing.T) {
d.Restart(t, "-s", driverName)
})
_, err := c.Info(ctx)
assert.NilError(t, err)
d.Stop(t)
// Don't check ec.exists, because the daemon no longer calls the
// Exists function.
assert.Check(t, is.Equal(ec[ext].activations, 2))
assert.Check(t, is.Equal(ec[ext].init, 2))
assert.Check(t, ec[ext].creations >= 1)
assert.Check(t, ec[ext].removals >= 1)
assert.Check(t, ec[ext].gets >= 1)
assert.Check(t, ec[ext].puts >= 1)
assert.Check(t, is.Equal(ec[ext].stats, 5))
assert.Check(t, is.Equal(ec[ext].cleanups, 2))
assert.Check(t, ec[ext].applydiff >= 1)
assert.Check(t, is.Equal(ec[ext].changes, 1))
assert.Check(t, is.Equal(ec[ext].diffsize, 0))
assert.Check(t, is.Equal(ec[ext].diff, 0))
assert.Check(t, is.Equal(ec[ext].metadata, 1))
}
}
}
func testGraphDriverPull(c client.APIClient, d *daemon.Daemon) func(*testing.T) {
return func(t *testing.T) {
d.Start(t)
defer d.Stop(t)
ctx := context.Background()
r, err := c.ImagePull(ctx, "busybox:latest@sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0", types.ImagePullOptions{})
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, r)
assert.NilError(t, err)
container.Run(t, ctx, c, container.WithImage("busybox:latest@sha256:bbc3a03235220b170ba48a157dd097dd1379299370e1ed99ce976df0355d24f0"))
}
}
func TestGraphdriverPluginV2(t *testing.T) {
skip.If(t, runtime.GOOS == "windows")
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
skip.If(t, !requirement.HasHubConnectivity(t))
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
skip.If(t, !requirement.Overlay2Supported(testEnv.DaemonInfo.KernelVersion))
d := daemon.New(t, daemon.WithExperimental)
d.Start(t)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
// install the plugin
plugin := "cpuguy83/docker-overlay2-graphdriver-plugin"
responseReader, err := client.PluginInstall(ctx, plugin, types.PluginInstallOptions{
RemoteRef: plugin,
AcceptAllPermissions: true,
})
defer responseReader.Close()
assert.NilError(t, err)
// ensure it's done by waiting for EOF on the response
_, err = io.Copy(ioutil.Discard, responseReader)
assert.NilError(t, err)
// restart the daemon with the plugin set as the storage driver
d.Stop(t)
d.StartWithBusybox(t, "-s", plugin, "--storage-opt", "overlay2.override_kernel_check=1")
testGraphDriver(t, client, ctx, plugin, nil)
}
// nolint: golint
func testGraphDriver(t *testing.T, c client.APIClient, ctx context.Context, driverName string, afterContainerRunFn func(*testing.T)) { //nolint: golint
id := container.Run(t, ctx, c, container.WithCmd("sh", "-c", "echo hello > /hello"))
if afterContainerRunFn != nil {
afterContainerRunFn(t)
}
i, err := c.ContainerInspect(ctx, id)
assert.NilError(t, err)
assert.Check(t, is.Equal(i.GraphDriver.Name, driverName))
diffs, err := c.ContainerDiff(ctx, id)
assert.NilError(t, err)
assert.Check(t, is.Contains(diffs, containertypes.ContainerChangeResponseItem{
Kind: archive.ChangeAdd,
Path: "/hello",
}), "diffs: %v", diffs)
err = c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
Force: true,
})
assert.NilError(t, err)
}

View file

@ -0,0 +1,36 @@
package graphdriver // import "github.com/docker/docker/integration/plugin/graphdriver"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
"github.com/docker/docker/pkg/reexec"
)
var (
testEnv *environment.Execution
)
func init() {
reexec.Init() // This is required for external graphdriver tests
}
const dockerdBinary = "dockerd"
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}

View file

@ -0,0 +1,48 @@
package main
import (
"encoding/json"
"fmt"
"net"
"net/http"
"os"
)
type start struct {
File string
}
func main() {
l, err := net.Listen("unix", "/run/docker/plugins/plugin.sock")
if err != nil {
panic(err)
}
mux := http.NewServeMux()
mux.HandleFunc("/LogDriver.StartLogging", func(w http.ResponseWriter, req *http.Request) {
startReq := &start{}
if err := json.NewDecoder(req.Body).Decode(startReq); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
f, err := os.OpenFile(startReq.File, os.O_RDONLY, 0600)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Close the file immediately, this allows us to test what happens in the daemon when the plugin has closed the
// file or, for example, the plugin has crashed.
f.Close()
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{}`)
})
server := http.Server{
Addr: l.Addr().String(),
Handler: mux,
}
server.Serve(l)
}

View file

@ -0,0 +1 @@
package cmd

View file

@ -0,0 +1,19 @@
package main
import (
"net"
"net/http"
)
func main() {
l, err := net.Listen("unix", "/run/docker/plugins/plugin.sock")
if err != nil {
panic(err)
}
server := http.Server{
Addr: l.Addr().String(),
Handler: http.NewServeMux(),
}
server.Serve(l)
}

View file

@ -0,0 +1 @@
package main

View file

@ -0,0 +1,67 @@
package logging
import (
"context"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/test/fixtures/plugin"
"github.com/docker/docker/pkg/locker"
"github.com/pkg/errors"
)
var pluginBuildLock = locker.New()
func ensurePlugin(t *testing.T, name string) string {
pluginBuildLock.Lock(name)
defer pluginBuildLock.Unlock(name)
installPath := filepath.Join(os.Getenv("GOPATH"), "bin", name)
if _, err := os.Stat(installPath); err == nil {
return installPath
}
goBin, err := exec.LookPath("go")
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(goBin, "build", "-o", installPath, "./"+filepath.Join("cmd", name))
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatal(errors.Wrapf(err, "error building basic plugin bin: %s", string(out)))
}
return installPath
}
func withSockPath(name string) func(*plugin.Config) {
return func(cfg *plugin.Config) {
cfg.Interface.Socket = name
}
}
func createPlugin(t *testing.T, client plugin.CreateClient, alias, bin string, opts ...plugin.CreateOpt) {
pluginBin := ensurePlugin(t, bin)
opts = append(opts, withSockPath("plugin.sock"))
opts = append(opts, plugin.WithBinary(pluginBin))
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
err := plugin.Create(ctx, client, alias, opts...)
cancel()
if err != nil {
t.Fatal(err)
}
}
func asLogDriver(cfg *plugin.Config) {
cfg.Interface.Types = []types.PluginInterfaceType{
{Capability: "logdriver", Prefix: "docker", Version: "1.0"},
}
}

View file

@ -0,0 +1,79 @@
package logging
import (
"bufio"
"context"
"os"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
"gotest.tools/skip"
)
func TestContinueAfterPluginCrash(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon(), "test requires daemon on the same host")
t.Parallel()
d := daemon.New(t)
d.StartWithBusybox(t, "--iptables=false", "--init")
defer d.Stop(t)
client := d.NewClientT(t)
createPlugin(t, client, "test", "close_on_start", asLogDriver)
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
assert.Assert(t, client.PluginEnable(ctx, "test", types.PluginEnableOptions{Timeout: 30}))
cancel()
defer client.PluginRemove(context.Background(), "test", types.PluginRemoveOptions{Force: true})
ctx, cancel = context.WithTimeout(context.Background(), 60*time.Second)
id := container.Run(t, ctx, client,
container.WithAutoRemove,
container.WithLogDriver("test"),
container.WithCmd(
"/bin/sh", "-c", "while true; do sleep 1; echo hello; done",
),
)
cancel()
defer client.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{Force: true})
// Attach to the container to make sure it's written a few times to stdout
attach, err := client.ContainerAttach(context.Background(), id, types.ContainerAttachOptions{Stream: true, Stdout: true})
assert.Assert(t, err)
chErr := make(chan error)
go func() {
defer close(chErr)
rdr := bufio.NewReader(attach.Reader)
for i := 0; i < 5; i++ {
_, _, err := rdr.ReadLine()
if err != nil {
chErr <- err
return
}
}
}()
select {
case err := <-chErr:
assert.Assert(t, err)
case <-time.After(60 * time.Second):
t.Fatal("timeout waiting for container i/o")
}
// check daemon logs for "broken pipe"
// TODO(@cpuguy83): This is horribly hacky but is the only way to really test this case right now.
// It would be nice if there was a way to know that a broken pipe has occurred without looking through the logs.
log, err := os.Open(d.LogFileName())
assert.Assert(t, err)
scanner := bufio.NewScanner(log)
for scanner.Scan() {
assert.Assert(t, !strings.Contains(scanner.Text(), "broken pipe"))
}
}

View file

@ -0,0 +1,29 @@
package logging // import "github.com/docker/docker/integration/plugin/logging"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var (
testEnv *environment.Execution
)
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}

View file

@ -0,0 +1,35 @@
package logging
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
"gotest.tools/skip"
)
// Regression test for #35553
// Ensure that a daemon with a log plugin set as the default logger for containers
// does not keep the daemon from starting.
func TestDaemonStartWithLogOpt(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
t.Parallel()
d := daemon.New(t)
d.Start(t, "--iptables=false")
defer d.Stop(t)
client, err := d.NewClient()
assert.Check(t, err)
ctx := context.Background()
createPlugin(t, client, "test", "dummy", asLogDriver)
err = client.PluginEnable(ctx, "test", types.PluginEnableOptions{Timeout: 30})
assert.Check(t, err)
defer client.PluginRemove(ctx, "test", types.PluginRemoveOptions{Force: true})
d.Stop(t)
d.Start(t, "--iptables=false", "--log-driver=test", "--log-opt=foo=bar")
}

View file

@ -0,0 +1 @@
package plugin // import "github.com/docker/docker/integration/plugin"

View file

@ -0,0 +1 @@
package cmd

View file

@ -0,0 +1,19 @@
package main
import (
"net"
"net/http"
)
func main() {
l, err := net.Listen("unix", "/run/docker/plugins/plugin.sock")
if err != nil {
panic(err)
}
server := http.Server{
Addr: l.Addr().String(),
Handler: http.NewServeMux(),
}
server.Serve(l)
}

View file

@ -0,0 +1 @@
package main

View file

@ -0,0 +1,73 @@
package volumes
import (
"context"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/test/fixtures/plugin"
"github.com/docker/docker/pkg/locker"
"github.com/pkg/errors"
)
var pluginBuildLock = locker.New()
// ensurePlugin makes the that a plugin binary has been installed on the system.
// Plugins that have not been installed are built from `cmd/<name>`.
func ensurePlugin(t *testing.T, name string) string {
pluginBuildLock.Lock(name)
defer pluginBuildLock.Unlock(name)
goPath := os.Getenv("GOPATH")
if goPath == "" {
goPath = "/go"
}
installPath := filepath.Join(goPath, "bin", name)
if _, err := os.Stat(installPath); err == nil {
return installPath
}
goBin, err := exec.LookPath("go")
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(goBin, "build", "-o", installPath, "./"+filepath.Join("cmd", name))
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatal(errors.Wrapf(err, "error building basic plugin bin: %s", string(out)))
}
return installPath
}
func withSockPath(name string) func(*plugin.Config) {
return func(cfg *plugin.Config) {
cfg.Interface.Socket = name
}
}
func createPlugin(t *testing.T, client plugin.CreateClient, alias, bin string, opts ...plugin.CreateOpt) {
pluginBin := ensurePlugin(t, bin)
opts = append(opts, withSockPath("plugin.sock"))
opts = append(opts, plugin.WithBinary(pluginBin))
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
err := plugin.Create(ctx, client, alias, opts...)
cancel()
if err != nil {
t.Fatal(err)
}
}
func asVolumeDriver(cfg *plugin.Config) {
cfg.Interface.Types = []types.PluginInterfaceType{
{Capability: "volumedriver", Prefix: "docker", Version: "1.0"},
}
}

View file

@ -0,0 +1,32 @@
package volumes // import "github.com/docker/docker/integration/plugin/volumes"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var (
testEnv *environment.Execution
)
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if testEnv.OSType != "linux" {
os.Exit(0)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}

View file

@ -0,0 +1,58 @@
package volumes
import (
"context"
"io/ioutil"
"os"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/internal/test/fixtures/plugin"
"gotest.tools/assert"
"gotest.tools/skip"
)
// TestPluginWithDevMounts tests very specific regression caused by mounts ordering
// (sorted in the daemon). See #36698
func TestPluginWithDevMounts(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
t.Parallel()
d := daemon.New(t)
d.Start(t, "--iptables=false")
defer d.Stop(t)
client, err := d.NewClient()
assert.Assert(t, err)
ctx := context.Background()
testDir, err := ioutil.TempDir("", "test-dir")
assert.Assert(t, err)
defer os.RemoveAll(testDir)
createPlugin(t, client, "test", "dummy", asVolumeDriver, func(c *plugin.Config) {
root := "/"
dev := "/dev"
mounts := []types.PluginMount{
{Type: "bind", Source: &root, Destination: "/host", Options: []string{"rbind"}},
{Type: "bind", Source: &dev, Destination: "/dev", Options: []string{"rbind"}},
{Type: "bind", Source: &testDir, Destination: "/etc/foo", Options: []string{"rbind"}},
}
c.PluginConfig.Mounts = append(c.PluginConfig.Mounts, mounts...)
c.PropagatedMount = "/propagated"
c.Network = types.PluginConfigNetwork{Type: "host"}
c.IpcHost = true
})
err = client.PluginEnable(ctx, "test", types.PluginEnableOptions{Timeout: 30})
assert.Assert(t, err)
defer func() {
err := client.PluginRemove(ctx, "test", types.PluginRemoveOptions{Force: true})
assert.Check(t, err)
}()
p, _, err := client.PluginInspectWithRaw(ctx, "test")
assert.Assert(t, err)
assert.Assert(t, p.Enabled)
}

View file

@ -0,0 +1,33 @@
package secret // import "github.com/docker/docker/integration/secret"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,366 @@
package secret // import "github.com/docker/docker/integration/secret"
import (
"bytes"
"context"
"sort"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/swarm"
"github.com/docker/docker/pkg/stdcopy"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestSecretInspect(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
testName := "test_secret_" + t.Name()
secretID := createSecret(ctx, t, client, testName, []byte("TESTINGDATA"), nil)
secret, _, err := client.SecretInspectWithRaw(context.Background(), secretID)
assert.NilError(t, err)
assert.Check(t, is.Equal(secret.Spec.Name, testName))
secret, _, err = client.SecretInspectWithRaw(context.Background(), testName)
assert.NilError(t, err)
assert.Check(t, is.Equal(secretID, secretID))
}
func TestSecretList(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
testName0 := "test0_" + t.Name()
testName1 := "test1_" + t.Name()
testNames := []string{testName0, testName1}
sort.Strings(testNames)
// create secret test0
createSecret(ctx, t, client, testName0, []byte("TESTINGDATA0"), map[string]string{"type": "test"})
// create secret test1
secret1ID := createSecret(ctx, t, client, testName1, []byte("TESTINGDATA1"), map[string]string{"type": "production"})
names := func(entries []swarmtypes.Secret) []string {
var values []string
for _, entry := range entries {
values = append(values, entry.Spec.Name)
}
sort.Strings(values)
return values
}
// test by `secret ls`
entries, err := client.SecretList(ctx, types.SecretListOptions{})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(names(entries), testNames))
testCases := []struct {
filters filters.Args
expected []string
}{
// test filter by name `secret ls --filter name=xxx`
{
filters: filters.NewArgs(filters.Arg("name", testName0)),
expected: []string{testName0},
},
// test filter by id `secret ls --filter id=xxx`
{
filters: filters.NewArgs(filters.Arg("id", secret1ID)),
expected: []string{testName1},
},
// test filter by label `secret ls --filter label=xxx`
{
filters: filters.NewArgs(filters.Arg("label", "type")),
expected: testNames,
},
{
filters: filters.NewArgs(filters.Arg("label", "type=test")),
expected: []string{testName0},
},
{
filters: filters.NewArgs(filters.Arg("label", "type=production")),
expected: []string{testName1},
},
}
for _, tc := range testCases {
entries, err = client.SecretList(ctx, types.SecretListOptions{
Filters: tc.filters,
})
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(names(entries), tc.expected))
}
}
func createSecret(ctx context.Context, t *testing.T, client client.APIClient, name string, data []byte, labels map[string]string) string {
secret, err := client.SecretCreate(ctx, swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{
Name: name,
Labels: labels,
},
Data: data,
})
assert.NilError(t, err)
assert.Check(t, secret.ID != "")
return secret.ID
}
func TestSecretsCreateAndDelete(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
testName := "test_secret_" + t.Name()
secretID := createSecret(ctx, t, client, testName, []byte("TESTINGDATA"), nil)
// create an already existin secret, daemon should return a status code of 409
_, err := client.SecretCreate(ctx, swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{
Name: testName,
},
Data: []byte("TESTINGDATA"),
})
assert.Check(t, is.ErrorContains(err, "already exists"))
// Ported from original TestSecretsDelete
err = client.SecretRemove(ctx, secretID)
assert.NilError(t, err)
_, _, err = client.SecretInspectWithRaw(ctx, secretID)
assert.Check(t, is.ErrorContains(err, "No such secret"))
err = client.SecretRemove(ctx, "non-existin")
assert.Check(t, is.ErrorContains(err, "No such secret: non-existin"))
// Ported from original TestSecretsCreteaWithLabels
testName = "test_secret_with_labels_" + t.Name()
secretID = createSecret(ctx, t, client, testName, []byte("TESTINGDATA"), map[string]string{
"key1": "value1",
"key2": "value2",
})
insp, _, err := client.SecretInspectWithRaw(ctx, secretID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Name, testName))
assert.Check(t, is.Equal(len(insp.Spec.Labels), 2))
assert.Check(t, is.Equal(insp.Spec.Labels["key1"], "value1"))
assert.Check(t, is.Equal(insp.Spec.Labels["key2"], "value2"))
}
func TestSecretsUpdate(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
testName := "test_secret_" + t.Name()
secretID := createSecret(ctx, t, client, testName, []byte("TESTINGDATA"), nil)
insp, _, err := client.SecretInspectWithRaw(ctx, secretID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.ID, secretID))
// test UpdateSecret with full ID
insp.Spec.Labels = map[string]string{"test": "test1"}
err = client.SecretUpdate(ctx, secretID, insp.Version, insp.Spec)
assert.NilError(t, err)
insp, _, err = client.SecretInspectWithRaw(ctx, secretID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test1"))
// test UpdateSecret with full name
insp.Spec.Labels = map[string]string{"test": "test2"}
err = client.SecretUpdate(ctx, testName, insp.Version, insp.Spec)
assert.NilError(t, err)
insp, _, err = client.SecretInspectWithRaw(ctx, secretID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test2"))
// test UpdateSecret with prefix ID
insp.Spec.Labels = map[string]string{"test": "test3"}
err = client.SecretUpdate(ctx, secretID[:1], insp.Version, insp.Spec)
assert.NilError(t, err)
insp, _, err = client.SecretInspectWithRaw(ctx, secretID)
assert.NilError(t, err)
assert.Check(t, is.Equal(insp.Spec.Labels["test"], "test3"))
// test UpdateSecret in updating Data which is not supported in daemon
// this test will produce an error in func UpdateSecret
insp.Spec.Data = []byte("TESTINGDATA2")
err = client.SecretUpdate(ctx, secretID, insp.Version, insp.Spec)
assert.Check(t, is.ErrorContains(err, "only updates to Labels are allowed"))
}
func TestTemplatedSecret(t *testing.T) {
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
referencedSecretName := "referencedsecret_" + t.Name()
referencedSecretSpec := swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{
Name: referencedSecretName,
},
Data: []byte("this is a secret"),
}
referencedSecret, err := client.SecretCreate(ctx, referencedSecretSpec)
assert.Check(t, err)
referencedConfigName := "referencedconfig_" + t.Name()
referencedConfigSpec := swarmtypes.ConfigSpec{
Annotations: swarmtypes.Annotations{
Name: referencedConfigName,
},
Data: []byte("this is a config"),
}
referencedConfig, err := client.ConfigCreate(ctx, referencedConfigSpec)
assert.Check(t, err)
templatedSecretName := "templated_secret_" + t.Name()
secretSpec := swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{
Name: templatedSecretName,
},
Templating: &swarmtypes.Driver{
Name: "golang",
},
Data: []byte("SERVICE_NAME={{.Service.Name}}\n" +
"{{secret \"referencedsecrettarget\"}}\n" +
"{{config \"referencedconfigtarget\"}}\n"),
}
templatedSecret, err := client.SecretCreate(ctx, secretSpec)
assert.Check(t, err)
serviceName := "svc_" + t.Name()
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithSecret(
&swarmtypes.SecretReference{
File: &swarmtypes.SecretReferenceFileTarget{
Name: "templated_secret",
UID: "0",
GID: "0",
Mode: 0600,
},
SecretID: templatedSecret.ID,
SecretName: templatedSecretName,
},
),
swarm.ServiceWithConfig(
&swarmtypes.ConfigReference{
File: &swarmtypes.ConfigReferenceFileTarget{
Name: "referencedconfigtarget",
UID: "0",
GID: "0",
Mode: 0600,
},
ConfigID: referencedConfig.ID,
ConfigName: referencedConfigName,
},
),
swarm.ServiceWithSecret(
&swarmtypes.SecretReference{
File: &swarmtypes.SecretReferenceFileTarget{
Name: "referencedsecrettarget",
UID: "0",
GID: "0",
Mode: 0600,
},
SecretID: referencedSecret.ID,
SecretName: referencedSecretName,
},
),
swarm.ServiceWithName(serviceName),
)
var tasks []swarmtypes.Task
waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
tasks = swarm.GetRunningTasks(t, d, serviceID)
return len(tasks) > 0
})
task := tasks[0]
waitAndAssert(t, 60*time.Second, func(t *testing.T) bool {
if task.NodeID == "" || (task.Status.ContainerStatus == nil || task.Status.ContainerStatus.ContainerID == "") {
task, _, _ = client.TaskInspectWithRaw(context.Background(), task.ID)
}
return task.NodeID != "" && task.Status.ContainerStatus != nil && task.Status.ContainerStatus.ContainerID != ""
})
attach := swarm.ExecTask(t, d, task, types.ExecConfig{
Cmd: []string{"/bin/cat", "/run/secrets/templated_secret"},
AttachStdout: true,
AttachStderr: true,
})
expect := "SERVICE_NAME=" + serviceName + "\n" +
"this is a secret\n" +
"this is a config\n"
assertAttachedStream(t, attach, expect)
attach = swarm.ExecTask(t, d, task, types.ExecConfig{
Cmd: []string{"mount"},
AttachStdout: true,
AttachStderr: true,
})
assertAttachedStream(t, attach, "tmpfs on /run/secrets/templated_secret type tmpfs")
}
func assertAttachedStream(t *testing.T, attach types.HijackedResponse, expect string) {
buf := bytes.NewBuffer(nil)
_, err := stdcopy.StdCopy(buf, buf, attach.Reader)
assert.NilError(t, err)
assert.Check(t, is.Contains(buf.String(), expect))
}
func waitAndAssert(t *testing.T, timeout time.Duration, f func(*testing.T) bool) {
t.Helper()
after := time.After(timeout)
for {
select {
case <-after:
t.Fatalf("timed out waiting for condition")
default:
}
if f(t) {
return
}
time.Sleep(100 * time.Millisecond)
}
}

View file

@ -0,0 +1,374 @@
package service // import "github.com/docker/docker/integration/service"
import (
"context"
"fmt"
"io/ioutil"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/integration/internal/swarm"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
)
func TestServiceCreateInit(t *testing.T) {
defer setupTest(t)()
t.Run("daemonInitDisabled", testServiceCreateInit(false))
t.Run("daemonInitEnabled", testServiceCreateInit(true))
}
func testServiceCreateInit(daemonEnabled bool) func(t *testing.T) {
return func(t *testing.T) {
var ops = []func(*daemon.Daemon){}
if daemonEnabled {
ops = append(ops, daemon.WithInit)
}
d := swarm.NewSwarm(t, testEnv, ops...)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
booleanTrue := true
booleanFalse := false
serviceID := swarm.CreateService(t, d)
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
i := inspectServiceContainer(t, client, serviceID)
// HostConfig.Init == nil means that it delegates to daemon configuration
assert.Check(t, i.HostConfig.Init == nil)
serviceID = swarm.CreateService(t, d, swarm.ServiceWithInit(&booleanTrue))
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
i = inspectServiceContainer(t, client, serviceID)
assert.Check(t, is.Equal(true, *i.HostConfig.Init))
serviceID = swarm.CreateService(t, d, swarm.ServiceWithInit(&booleanFalse))
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, 1), swarm.ServicePoll)
i = inspectServiceContainer(t, client, serviceID)
assert.Check(t, is.Equal(false, *i.HostConfig.Init))
}
}
func inspectServiceContainer(t *testing.T, client client.APIClient, serviceID string) types.ContainerJSON {
t.Helper()
filter := filters.NewArgs()
filter.Add("label", fmt.Sprintf("com.docker.swarm.service.id=%s", serviceID))
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{Filters: filter})
assert.NilError(t, err)
assert.Check(t, is.Len(containers, 1))
i, err := client.ContainerInspect(context.Background(), containers[0].ID)
assert.NilError(t, err)
return i
}
func TestCreateServiceMultipleTimes(t *testing.T) {
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
overlayName := "overlay1_" + t.Name()
overlayID := network.CreateNoError(t, context.Background(), client, overlayName,
network.WithCheckDuplicate(),
network.WithDriver("overlay"),
)
var instances uint64 = 4
serviceName := "TestService_" + t.Name()
serviceSpec := []swarm.ServiceSpecOpt{
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(serviceName),
swarm.ServiceWithNetwork(overlayName),
}
serviceID := swarm.CreateService(t, d, serviceSpec...)
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, instances), swarm.ServicePoll)
_, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
assert.NilError(t, err)
err = client.ServiceRemove(context.Background(), serviceID)
assert.NilError(t, err)
poll.WaitOn(t, serviceIsRemoved(client, serviceID), swarm.ServicePoll)
poll.WaitOn(t, noTasks(client), swarm.ServicePoll)
serviceID2 := swarm.CreateService(t, d, serviceSpec...)
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID2, instances), swarm.ServicePoll)
err = client.ServiceRemove(context.Background(), serviceID2)
assert.NilError(t, err)
poll.WaitOn(t, serviceIsRemoved(client, serviceID2), swarm.ServicePoll)
poll.WaitOn(t, noTasks(client), swarm.ServicePoll)
err = client.NetworkRemove(context.Background(), overlayID)
assert.NilError(t, err)
poll.WaitOn(t, networkIsRemoved(client, overlayID), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
}
func TestCreateWithDuplicateNetworkNames(t *testing.T) {
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
name := "foo_" + t.Name()
n1 := network.CreateNoError(t, context.Background(), client, name,
network.WithDriver("bridge"),
)
n2 := network.CreateNoError(t, context.Background(), client, name,
network.WithDriver("bridge"),
)
// Dupliates with name but with different driver
n3 := network.CreateNoError(t, context.Background(), client, name,
network.WithDriver("overlay"),
)
// Create Service with the same name
var instances uint64 = 1
serviceName := "top_" + t.Name()
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(serviceName),
swarm.ServiceWithNetwork(name),
)
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, instances), swarm.ServicePoll)
resp, _, err := client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(n3, resp.Spec.TaskTemplate.Networks[0].Target))
// Remove Service
err = client.ServiceRemove(context.Background(), serviceID)
assert.NilError(t, err)
// Make sure task has been destroyed.
poll.WaitOn(t, serviceIsRemoved(client, serviceID), swarm.ServicePoll)
// Remove networks
err = client.NetworkRemove(context.Background(), n3)
assert.NilError(t, err)
err = client.NetworkRemove(context.Background(), n2)
assert.NilError(t, err)
err = client.NetworkRemove(context.Background(), n1)
assert.NilError(t, err)
// Make sure networks have been destroyed.
poll.WaitOn(t, networkIsRemoved(client, n3), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
poll.WaitOn(t, networkIsRemoved(client, n2), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
poll.WaitOn(t, networkIsRemoved(client, n1), poll.WithTimeout(1*time.Minute), poll.WithDelay(10*time.Second))
}
func TestCreateServiceSecretFileMode(t *testing.T) {
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
secretName := "TestSecret_" + t.Name()
secretResp, err := client.SecretCreate(ctx, swarmtypes.SecretSpec{
Annotations: swarmtypes.Annotations{
Name: secretName,
},
Data: []byte("TESTSECRET"),
})
assert.NilError(t, err)
var instances uint64 = 1
serviceName := "TestService_" + t.Name()
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithName(serviceName),
swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/secret || /bin/top"}),
swarm.ServiceWithSecret(&swarmtypes.SecretReference{
File: &swarmtypes.SecretReferenceFileTarget{
Name: "/etc/secret",
UID: "0",
GID: "0",
Mode: 0777,
},
SecretID: secretResp.ID,
SecretName: secretName,
}),
)
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, instances), swarm.ServicePoll)
filter := filters.NewArgs()
filter.Add("service", serviceID)
tasks, err := client.TaskList(ctx, types.TaskListOptions{
Filters: filter,
})
assert.NilError(t, err)
assert.Check(t, is.Equal(len(tasks), 1))
body, err := client.ContainerLogs(ctx, tasks[0].Status.ContainerStatus.ContainerID, types.ContainerLogsOptions{
ShowStdout: true,
})
assert.NilError(t, err)
defer body.Close()
content, err := ioutil.ReadAll(body)
assert.NilError(t, err)
assert.Check(t, is.Contains(string(content), "-rwxrwxrwx"))
err = client.ServiceRemove(ctx, serviceID)
assert.NilError(t, err)
poll.WaitOn(t, serviceIsRemoved(client, serviceID), swarm.ServicePoll)
poll.WaitOn(t, noTasks(client), swarm.ServicePoll)
err = client.SecretRemove(ctx, secretName)
assert.NilError(t, err)
}
func TestCreateServiceConfigFileMode(t *testing.T) {
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
configName := "TestConfig_" + t.Name()
configResp, err := client.ConfigCreate(ctx, swarmtypes.ConfigSpec{
Annotations: swarmtypes.Annotations{
Name: configName,
},
Data: []byte("TESTCONFIG"),
})
assert.NilError(t, err)
var instances uint64 = 1
serviceName := "TestService_" + t.Name()
serviceID := swarm.CreateService(t, d,
swarm.ServiceWithName(serviceName),
swarm.ServiceWithCommand([]string{"/bin/sh", "-c", "ls -l /etc/config || /bin/top"}),
swarm.ServiceWithReplicas(instances),
swarm.ServiceWithConfig(&swarmtypes.ConfigReference{
File: &swarmtypes.ConfigReferenceFileTarget{
Name: "/etc/config",
UID: "0",
GID: "0",
Mode: 0777,
},
ConfigID: configResp.ID,
ConfigName: configName,
}),
)
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, instances))
filter := filters.NewArgs()
filter.Add("service", serviceID)
tasks, err := client.TaskList(ctx, types.TaskListOptions{
Filters: filter,
})
assert.NilError(t, err)
assert.Check(t, is.Equal(len(tasks), 1))
body, err := client.ContainerLogs(ctx, tasks[0].Status.ContainerStatus.ContainerID, types.ContainerLogsOptions{
ShowStdout: true,
})
assert.NilError(t, err)
defer body.Close()
content, err := ioutil.ReadAll(body)
assert.NilError(t, err)
assert.Check(t, is.Contains(string(content), "-rwxrwxrwx"))
err = client.ServiceRemove(ctx, serviceID)
assert.NilError(t, err)
poll.WaitOn(t, serviceIsRemoved(client, serviceID))
poll.WaitOn(t, noTasks(client))
err = client.ConfigRemove(ctx, configName)
assert.NilError(t, err)
}
func serviceRunningTasksCount(client client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
filter := filters.NewArgs()
filter.Add("service", serviceID)
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filter,
})
switch {
case err != nil:
return poll.Error(err)
case len(tasks) == int(instances):
for _, task := range tasks {
if task.Status.State != swarmtypes.TaskStateRunning {
return poll.Continue("waiting for tasks to enter run state")
}
}
return poll.Success()
default:
return poll.Continue("task count at %d waiting for %d", len(tasks), instances)
}
}
}
func noTasks(client client.ServiceAPIClient) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
filter := filters.NewArgs()
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filter,
})
switch {
case err != nil:
return poll.Error(err)
case len(tasks) == 0:
return poll.Success()
default:
return poll.Continue("task count at %d waiting for 0", len(tasks))
}
}
}
func serviceIsRemoved(client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
filter := filters.NewArgs()
filter.Add("service", serviceID)
_, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filter,
})
if err == nil {
return poll.Continue("waiting for service %s to be deleted", serviceID)
}
return poll.Success()
}
}
func networkIsRemoved(client client.NetworkAPIClient, networkID string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
_, err := client.NetworkInspect(context.Background(), networkID, types.NetworkInspectOptions{})
if err == nil {
return poll.Continue("waiting for network %s to be removed", networkID)
}
return poll.Success()
}
}

View file

@ -0,0 +1,153 @@
package service // import "github.com/docker/docker/integration/service"
import (
"context"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/swarm"
"github.com/google/go-cmp/cmp"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestInspect(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
var now = time.Now()
var instances uint64 = 2
serviceSpec := fullSwarmServiceSpec("test-service-inspect", instances)
ctx := context.Background()
resp, err := client.ServiceCreate(ctx, serviceSpec, types.ServiceCreateOptions{
QueryRegistry: false,
})
assert.NilError(t, err)
id := resp.ID
poll.WaitOn(t, serviceContainerCount(client, id, instances))
service, _, err := client.ServiceInspectWithRaw(ctx, id, types.ServiceInspectOptions{})
assert.NilError(t, err)
expected := swarmtypes.Service{
ID: id,
Spec: serviceSpec,
Meta: swarmtypes.Meta{
Version: swarmtypes.Version{Index: uint64(11)},
CreatedAt: now,
UpdatedAt: now,
},
}
assert.Check(t, is.DeepEqual(service, expected, cmpServiceOpts()))
}
// TODO: use helpers from gotest.tools/assert/opt when available
func cmpServiceOpts() cmp.Option {
const threshold = 20 * time.Second
metaTimeFields := func(path cmp.Path) bool {
switch path.String() {
case "Meta.CreatedAt", "Meta.UpdatedAt":
return true
}
return false
}
withinThreshold := cmp.Comparer(func(x, y time.Time) bool {
delta := x.Sub(y)
return delta < threshold && delta > -threshold
})
return cmp.FilterPath(metaTimeFields, withinThreshold)
}
func fullSwarmServiceSpec(name string, replicas uint64) swarmtypes.ServiceSpec {
restartDelay := 100 * time.Millisecond
maxAttempts := uint64(4)
return swarmtypes.ServiceSpec{
Annotations: swarmtypes.Annotations{
Name: name,
Labels: map[string]string{
"service-label": "service-label-value",
},
},
TaskTemplate: swarmtypes.TaskSpec{
ContainerSpec: &swarmtypes.ContainerSpec{
Image: "busybox:latest",
Labels: map[string]string{"container-label": "container-value"},
Command: []string{"/bin/top"},
Args: []string{"-u", "root"},
Hostname: "hostname",
Env: []string{"envvar=envvalue"},
Dir: "/work",
User: "root",
StopSignal: "SIGINT",
StopGracePeriod: &restartDelay,
Hosts: []string{"8.8.8.8 google"},
DNSConfig: &swarmtypes.DNSConfig{
Nameservers: []string{"8.8.8.8"},
Search: []string{"somedomain"},
},
Isolation: container.IsolationDefault,
},
RestartPolicy: &swarmtypes.RestartPolicy{
Delay: &restartDelay,
Condition: swarmtypes.RestartPolicyConditionOnFailure,
MaxAttempts: &maxAttempts,
},
Runtime: swarmtypes.RuntimeContainer,
},
Mode: swarmtypes.ServiceMode{
Replicated: &swarmtypes.ReplicatedService{
Replicas: &replicas,
},
},
UpdateConfig: &swarmtypes.UpdateConfig{
Parallelism: 2,
Delay: 200 * time.Second,
FailureAction: swarmtypes.UpdateFailureActionContinue,
Monitor: 2 * time.Second,
MaxFailureRatio: 0.2,
Order: swarmtypes.UpdateOrderStopFirst,
},
RollbackConfig: &swarmtypes.UpdateConfig{
Parallelism: 3,
Delay: 300 * time.Second,
FailureAction: swarmtypes.UpdateFailureActionPause,
Monitor: 3 * time.Second,
MaxFailureRatio: 0.3,
Order: swarmtypes.UpdateOrderStartFirst,
},
}
}
func serviceContainerCount(client client.ServiceAPIClient, id string, count uint64) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
filter := filters.NewArgs()
filter.Add("service", id)
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filter,
})
switch {
case err != nil:
return poll.Error(err)
case len(tasks) == int(count):
return poll.Success()
default:
return poll.Continue("task count at %d waiting for %d", len(tasks), count)
}
}
}

View file

@ -0,0 +1,33 @@
package service // import "github.com/docker/docker/integration/service"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,75 @@
package service // import "github.com/docker/docker/integration/service"
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/integration/internal/container"
net "github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/integration/internal/swarm"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestDockerNetworkConnectAlias(t *testing.T) {
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
client := d.NewClientT(t)
defer client.Close()
ctx := context.Background()
name := t.Name() + "test-alias"
net.CreateNoError(t, ctx, client, name,
net.WithDriver("overlay"),
net.WithAttachable(),
)
cID1 := container.Create(t, ctx, client, func(c *container.TestContainerConfig) {
c.NetworkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
name: {},
},
}
})
err := client.NetworkConnect(ctx, name, cID1, &network.EndpointSettings{
Aliases: []string{
"aaa",
},
})
assert.NilError(t, err)
err = client.ContainerStart(ctx, cID1, types.ContainerStartOptions{})
assert.NilError(t, err)
ng1, err := client.ContainerInspect(ctx, cID1)
assert.NilError(t, err)
assert.Check(t, is.Equal(len(ng1.NetworkSettings.Networks[name].Aliases), 2))
assert.Check(t, is.Equal(ng1.NetworkSettings.Networks[name].Aliases[0], "aaa"))
cID2 := container.Create(t, ctx, client, func(c *container.TestContainerConfig) {
c.NetworkingConfig = &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{
name: {},
},
}
})
err = client.NetworkConnect(ctx, name, cID2, &network.EndpointSettings{
Aliases: []string{
"bbb",
},
})
assert.NilError(t, err)
err = client.ContainerStart(ctx, cID2, types.ContainerStartOptions{})
assert.NilError(t, err)
ng2, err := client.ContainerInspect(ctx, cID2)
assert.NilError(t, err)
assert.Check(t, is.Equal(len(ng2.NetworkSettings.Networks[name].Aliases), 2))
assert.Check(t, is.Equal(ng2.NetworkSettings.Networks[name].Aliases[0], "bbb"))
}

View file

@ -0,0 +1,121 @@
package service
import (
"context"
"io"
"io/ioutil"
"os"
"path"
"testing"
"github.com/docker/docker/api/types"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/swarm/runtime"
"github.com/docker/docker/integration/internal/swarm"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/internal/test/fixtures/plugin"
"github.com/docker/docker/internal/test/registry"
"gotest.tools/assert"
"gotest.tools/poll"
"gotest.tools/skip"
)
func TestServicePlugin(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
skip.If(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
defer setupTest(t)()
reg := registry.NewV2(t)
defer reg.Close()
repo := path.Join(registry.DefaultURL, "swarm", "test:v1")
repo2 := path.Join(registry.DefaultURL, "swarm", "test:v2")
name := "test"
d := daemon.New(t)
d.StartWithBusybox(t)
apiclient := d.NewClientT(t)
err := plugin.Create(context.Background(), apiclient, repo)
assert.NilError(t, err)
r, err := apiclient.PluginPush(context.Background(), repo, "")
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, r)
assert.NilError(t, err)
err = apiclient.PluginRemove(context.Background(), repo, types.PluginRemoveOptions{})
assert.NilError(t, err)
err = plugin.Create(context.Background(), apiclient, repo2)
assert.NilError(t, err)
r, err = apiclient.PluginPush(context.Background(), repo2, "")
assert.NilError(t, err)
_, err = io.Copy(ioutil.Discard, r)
assert.NilError(t, err)
err = apiclient.PluginRemove(context.Background(), repo2, types.PluginRemoveOptions{})
assert.NilError(t, err)
d.Stop(t)
d1 := swarm.NewSwarm(t, testEnv, daemon.WithExperimental)
defer d1.Stop(t)
d2 := daemon.New(t, daemon.WithExperimental, daemon.WithSwarmPort(daemon.DefaultSwarmPort+1))
d2.StartAndSwarmJoin(t, d1, true)
defer d2.Stop(t)
d3 := daemon.New(t, daemon.WithExperimental, daemon.WithSwarmPort(daemon.DefaultSwarmPort+2))
d3.StartAndSwarmJoin(t, d1, false)
defer d3.Stop(t)
id := d1.CreateService(t, makePlugin(repo, name, nil))
poll.WaitOn(t, d1.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsRunning(name), swarm.ServicePoll)
service := d1.GetService(t, id)
d1.UpdateService(t, service, makePlugin(repo2, name, nil))
poll.WaitOn(t, d1.PluginReferenceIs(name, repo2), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginReferenceIs(name, repo2), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginReferenceIs(name, repo2), swarm.ServicePoll)
poll.WaitOn(t, d1.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsRunning(name), swarm.ServicePoll)
d1.RemoveService(t, id)
poll.WaitOn(t, d1.PluginIsNotPresent(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsNotPresent(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsNotPresent(name), swarm.ServicePoll)
// constrain to managers only
id = d1.CreateService(t, makePlugin(repo, name, []string{"node.role==manager"}))
poll.WaitOn(t, d1.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsRunning(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsNotPresent(name), swarm.ServicePoll)
d1.RemoveService(t, id)
poll.WaitOn(t, d1.PluginIsNotPresent(name), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsNotPresent(name), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsNotPresent(name), swarm.ServicePoll)
// with no name
id = d1.CreateService(t, makePlugin(repo, "", nil))
poll.WaitOn(t, d1.PluginIsRunning(repo), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsRunning(repo), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsRunning(repo), swarm.ServicePoll)
d1.RemoveService(t, id)
poll.WaitOn(t, d1.PluginIsNotPresent(repo), swarm.ServicePoll)
poll.WaitOn(t, d2.PluginIsNotPresent(repo), swarm.ServicePoll)
poll.WaitOn(t, d3.PluginIsNotPresent(repo), swarm.ServicePoll)
}
func makePlugin(repo, name string, constraints []string) func(*swarmtypes.Service) {
return func(s *swarmtypes.Service) {
s.Spec.TaskTemplate.Runtime = swarmtypes.RuntimePlugin
s.Spec.TaskTemplate.PluginSpec = &runtime.PluginSpec{
Name: name,
Remote: repo,
}
if constraints != nil {
s.Spec.TaskTemplate.Placement = &swarmtypes.Placement{
Constraints: constraints,
}
}
}
}

View file

@ -0,0 +1,33 @@
package session // import "github.com/docker/docker/integration/session"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,48 @@
package session // import "github.com/docker/docker/integration/session"
import (
"net/http"
"testing"
req "github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestSessionCreate(t *testing.T) {
skip.If(t, !testEnv.DaemonInfo.ExperimentalBuild)
defer setupTest(t)()
res, body, err := req.Post("/session", req.With(func(r *http.Request) error {
r.Header.Set("X-Docker-Expose-Session-Uuid", "testsessioncreate") // so we don't block default name if something else is using it
r.Header.Set("Upgrade", "h2c")
return nil
}))
assert.NilError(t, err)
assert.NilError(t, body.Close())
assert.Check(t, is.DeepEqual(res.StatusCode, http.StatusSwitchingProtocols))
assert.Check(t, is.Equal(res.Header.Get("Upgrade"), "h2c"))
}
func TestSessionCreateWithBadUpgrade(t *testing.T) {
skip.If(t, !testEnv.DaemonInfo.ExperimentalBuild)
res, body, err := req.Post("/session")
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(res.StatusCode, http.StatusBadRequest))
buf, err := req.ReadBody(body)
assert.NilError(t, err)
assert.Check(t, is.Contains(string(buf), "no upgrade"))
res, body, err = req.Post("/session", req.With(func(r *http.Request) error {
r.Header.Set("Upgrade", "foo")
return nil
}))
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(res.StatusCode, http.StatusBadRequest))
buf, err = req.ReadBody(body)
assert.NilError(t, err)
assert.Check(t, is.Contains(string(buf), "not supported"))
}

View file

@ -0,0 +1,56 @@
package system
import (
"context"
"os"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/daemon"
"gotest.tools/assert"
)
// hasSystemd checks whether the host was booted with systemd as its init
// system. Stolen from
// https://github.com/coreos/go-systemd/blob/176f85496f4e/util/util.go#L68
func hasSystemd() bool {
fi, err := os.Lstat("/run/systemd/system")
if err != nil {
return false
}
return fi.IsDir()
}
// TestCgroupDriverSystemdMemoryLimit checks that container
// memory limit can be set when using systemd cgroupdriver.
// https://github.com/moby/moby/issues/35123
func TestCgroupDriverSystemdMemoryLimit(t *testing.T) {
t.Parallel()
if !hasSystemd() {
t.Skip("systemd not available")
}
d := daemon.New(t)
client, err := d.NewClient()
assert.NilError(t, err)
d.StartWithBusybox(t, "--exec-opt", "native.cgroupdriver=systemd", "--iptables=false")
defer d.Stop(t)
const mem = 64 * 1024 * 1024 // 64 MB
ctx := context.Background()
ctrID := container.Create(t, ctx, client, func(c *container.TestContainerConfig) {
c.HostConfig.Resources.Memory = mem
})
defer client.ContainerRemove(ctx, ctrID, types.ContainerRemoveOptions{Force: true})
err = client.ContainerStart(ctx, ctrID, types.ContainerStartOptions{})
assert.NilError(t, err)
s, err := client.ContainerInspect(ctx, ctrID)
assert.NilError(t, err)
assert.Equal(t, s.HostConfig.Memory, mem)
}

View file

@ -0,0 +1,122 @@
package system // import "github.com/docker/docker/integration/system"
import (
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
req "github.com/docker/docker/internal/test/request"
"github.com/docker/docker/pkg/jsonmessage"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestEventsExecDie(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.36"), "broken in earlier versions")
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
cID := container.Run(t, ctx, client)
id, err := client.ContainerExecCreate(ctx, cID,
types.ExecConfig{
Cmd: strslice.StrSlice([]string{"echo", "hello"}),
},
)
assert.NilError(t, err)
filters := filters.NewArgs(
filters.Arg("container", cID),
filters.Arg("event", "exec_die"),
)
msg, errors := client.Events(ctx, types.EventsOptions{
Filters: filters,
})
err = client.ContainerExecStart(ctx, id.ID,
types.ExecStartCheck{
Detach: true,
Tty: false,
},
)
assert.NilError(t, err)
select {
case m := <-msg:
assert.Equal(t, m.Type, "container")
assert.Equal(t, m.Actor.ID, cID)
assert.Equal(t, m.Action, "exec_die")
assert.Equal(t, m.Actor.Attributes["execID"], id.ID)
assert.Equal(t, m.Actor.Attributes["exitCode"], "0")
case err = <-errors:
t.Fatal(err)
case <-time.After(time.Second * 3):
t.Fatal("timeout hit")
}
}
// Test case for #18888: Events messages have been switched from generic
// `JSONMessage` to `events.Message` types. The switch does not break the
// backward compatibility so old `JSONMessage` could still be used.
// This test verifies that backward compatibility maintains.
func TestEventsBackwardsCompatible(t *testing.T) {
defer setupTest(t)()
ctx := context.Background()
client := request.NewAPIClient(t)
since := request.DaemonTime(ctx, t, client, testEnv)
ts := strconv.FormatInt(since.Unix(), 10)
cID := container.Create(t, ctx, client)
// In case there is no events, the API should have responded immediately (not blocking),
// The test here makes sure the response time is less than 3 sec.
expectedTime := time.Now().Add(3 * time.Second)
emptyResp, emptyBody, err := req.Get("/events")
assert.NilError(t, err)
defer emptyBody.Close()
assert.Check(t, is.DeepEqual(http.StatusOK, emptyResp.StatusCode))
assert.Check(t, time.Now().Before(expectedTime), "timeout waiting for events api to respond, should have responded immediately")
// We also test to make sure the `events.Message` is compatible with `JSONMessage`
q := url.Values{}
q.Set("since", ts)
_, body, err := req.Get("/events?" + q.Encode())
assert.NilError(t, err)
defer body.Close()
dec := json.NewDecoder(body)
var containerCreateEvent *jsonmessage.JSONMessage
for {
var event jsonmessage.JSONMessage
if err := dec.Decode(&event); err != nil {
if err == io.EOF {
break
}
t.Fatal(err)
}
if event.Status == "create" && event.ID == cID {
containerCreateEvent = &event
break
}
}
assert.Check(t, containerCreateEvent != nil)
assert.Check(t, is.Equal("create", containerCreateEvent.Status))
assert.Check(t, is.Equal(cID, containerCreateEvent.ID))
assert.Check(t, is.Equal("busybox", containerCreateEvent.From))
}

View file

@ -0,0 +1,48 @@
// +build !windows
package system // import "github.com/docker/docker/integration/system"
import (
"context"
"net/http"
"testing"
"github.com/docker/docker/internal/test/request"
req "github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestInfoBinaryCommits(t *testing.T) {
client := request.NewAPIClient(t)
info, err := client.Info(context.Background())
assert.NilError(t, err)
assert.Check(t, "N/A" != info.ContainerdCommit.ID)
assert.Check(t, is.Equal(testEnv.DaemonInfo.ContainerdCommit.Expected, info.ContainerdCommit.Expected))
assert.Check(t, is.Equal(info.ContainerdCommit.Expected, info.ContainerdCommit.ID))
assert.Check(t, "N/A" != info.InitCommit.ID)
assert.Check(t, is.Equal(testEnv.DaemonInfo.InitCommit.Expected, info.InitCommit.Expected))
assert.Check(t, is.Equal(info.InitCommit.Expected, info.InitCommit.ID))
assert.Check(t, "N/A" != info.RuncCommit.ID)
assert.Check(t, is.Equal(testEnv.DaemonInfo.RuncCommit.Expected, info.RuncCommit.Expected))
assert.Check(t, is.Equal(info.RuncCommit.Expected, info.RuncCommit.ID))
}
func TestInfoAPIVersioned(t *testing.T) {
// Windows only supports 1.25 or later
res, body, err := req.Get("/v1.20/info")
assert.NilError(t, err)
assert.Check(t, is.DeepEqual(res.StatusCode, http.StatusOK))
b, err := req.ReadBody(body)
assert.NilError(t, err)
out := string(b)
assert.Check(t, is.Contains(out, "ExecutionDriver"))
assert.Check(t, is.Contains(out, "not supported"))
}

View file

@ -0,0 +1,66 @@
package system // import "github.com/docker/docker/integration/system"
import (
"context"
"fmt"
"testing"
"github.com/docker/docker/internal/test/daemon"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestInfoAPI(t *testing.T) {
client := request.NewAPIClient(t)
info, err := client.Info(context.Background())
assert.NilError(t, err)
// always shown fields
stringsToCheck := []string{
"ID",
"Containers",
"ContainersRunning",
"ContainersPaused",
"ContainersStopped",
"Images",
"LoggingDriver",
"OperatingSystem",
"NCPU",
"OSType",
"Architecture",
"MemTotal",
"KernelVersion",
"Driver",
"ServerVersion",
"SecurityOptions"}
out := fmt.Sprintf("%+v", info)
for _, linePrefix := range stringsToCheck {
assert.Check(t, is.Contains(out, linePrefix))
}
}
func TestInfoAPIWarnings(t *testing.T) {
d := daemon.New(t)
client, err := d.NewClient()
assert.NilError(t, err)
d.StartWithBusybox(t, "--iptables=false", "-H=0.0.0.0:23756", "-H=unix://"+d.Sock())
defer d.Stop(t)
info, err := client.Info(context.Background())
assert.NilError(t, err)
stringsToCheck := []string{
"Access to the remote API is equivalent to root access",
"http://0.0.0.0:23756",
}
out := fmt.Sprintf("%+v", info)
for _, linePrefix := range stringsToCheck {
assert.Check(t, is.Contains(out, linePrefix))
}
}

View file

@ -0,0 +1,28 @@
package system // import "github.com/docker/docker/integration/system"
import (
"context"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/requirement"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
// Test case for GitHub 22244
func TestLoginFailsWithBadCredentials(t *testing.T) {
skip.If(t, !requirement.HasHubConnectivity(t))
client := request.NewAPIClient(t)
config := types.AuthConfig{
Username: "no-user",
Password: "no-password",
}
_, err := client.RegistryLogin(context.Background(), config)
expected := "Error response from daemon: Get https://registry-1.docker.io/v2/: unauthorized: incorrect username or password"
assert.Check(t, is.Error(err, expected))
}

View file

@ -0,0 +1,33 @@
package system // import "github.com/docker/docker/integration/system"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,23 @@
package system // import "github.com/docker/docker/integration/system"
import (
"context"
"testing"
"github.com/docker/docker/internal/test/request"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestVersion(t *testing.T) {
client := request.NewAPIClient(t)
version, err := client.ServerVersion(context.Background())
assert.NilError(t, err)
assert.Check(t, version.APIVersion != "")
assert.Check(t, version.Version != "")
assert.Check(t, version.MinAPIVersion != "")
assert.Check(t, is.Equal(testEnv.DaemonInfo.ExperimentalBuild, version.Experimental))
assert.Check(t, is.Equal(testEnv.OSType, version.Os))
}

View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID0TCCAzqgAwIBAgIJAP2r7GqEJwSnMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMG
A1UEChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMI
Y2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWls
QGhvc3QuZG9tYWluMB4XDTEzMTIwMzE2NTYzMFoXDTIzMTIwMTE2NTYzMFowgaIx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2Nv
MRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYD
VQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEW
EG1haWxAaG9zdC5kb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAn
0xDw+5y7ZptQacq66pUhRu82JP2WU6IDgo5QUtNU6/CX5PwQATe/OnYTZQFbksxp
AU9boG0FCkgxfsgPYXEuZxVEGKI2fxfKHOZZI8mrkWmj6eWU/0cvCjGVc9rTITP5
sNQvg+hORyVDdNp2IdsbMJayiB3AQYMFx3vSDOMTAgMBAAGjggELMIIBBzAdBgNV
HQ4EFgQUZu7DFz09q0QBa2+ymRm9qgK1NPswgdcGA1UdIwSBzzCBzIAUZu7DFz09
q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
hCcEpzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAF8fJKKM+/oOdnNi
zEd0M1+PmZOyqvjYQn/2ZR8UHH6Imgc/OPQKZXf0bVE1Txc/DaUNn9Isd1SuCuaE
ic3vAIYYU7PmgeNN6vwec48V96T7jr+GAi6AVMhQEc2hHCfVtx11Xx+x6aHDZzJt
Zxtf5lL6KSO9Y+EFwM+rju6hm5hW
-----END CERTIFICATE-----

View file

@ -0,0 +1,73 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 3 (0x3)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
Validity
Not Before: Dec 4 14:17:54 2013 GMT
Not After : Dec 2 14:17:54 2023 GMT
Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (1024 bit)
Modulus:
00:ca:c9:05:d0:09:4e:3e:a4:fc:d5:14:f4:a5:e8:
34:d3:6b:51:e3:f3:62:ea:a1:f0:e8:ed:c4:2a:bc:
f0:4f:ca:07:df:e3:88:fa:f4:21:99:35:0e:3d:ea:
b0:86:e7:c4:d2:8a:83:2b:42:b8:ec:a3:99:62:70:
81:46:cc:fc:a5:1d:d2:63:e8:eb:07:25:9a:e2:25:
6d:11:56:f2:1a:51:a1:b6:3e:1c:57:32:e9:7b:2c:
aa:1b:cc:97:2d:89:2d:b1:c9:5e:35:28:4d:7c:fa:
65:31:3e:f7:70:dd:6e:0b:3c:58:af:a8:2e:24:c0:
7e:4e:78:7d:0a:9e:8f:42:43
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
Easy-RSA Generated Certificate
X509v3 Subject Key Identifier:
DE:42:EF:2D:98:A3:6C:A8:AA:E0:8C:71:2C:9D:64:23:A9:E2:7E:81
X509v3 Authority Key Identifier:
keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
serial:FD:AB:EC:6A:84:27:04:A7
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Key Usage:
Digital Signature
Signature Algorithm: sha1WithRSAEncryption
1c:44:26:ea:e1:66:25:cb:e4:8e:57:1c:f6:b9:17:22:62:40:
12:90:8f:3b:b2:61:7a:54:94:8f:b1:20:0b:bf:a3:51:e3:fa:
1c:a1:be:92:3a:d0:76:44:c0:57:83:ab:6a:e4:1a:45:49:a4:
af:39:0d:60:32:fc:3a:be:d7:fb:5d:99:7a:1f:87:e7:d5:ab:
84:a2:5e:90:d8:bf:fa:89:6d:32:26:02:5e:31:35:68:7f:31:
f5:6b:51:46:bc:af:70:ed:5a:09:7d:ec:b2:48:4f:fe:c5:2f:
56:04:ad:f6:c1:d2:2a:e4:6a:c4:87:fe:08:35:c5:38:cb:5e:
4a:c4
-----BEGIN CERTIFICATE-----
MIIEFTCCA36gAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
bWFpbjAeFw0xMzEyMDQxNDE3NTRaFw0yMzEyMDIxNDE3NTRaMIGgMQswCQYDVQQG
EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEPMA0GA1UEAxMGY2xp
ZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0
LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyskF0AlOPqT81RT0
peg002tR4/Ni6qHw6O3EKrzwT8oH3+OI+vQhmTUOPeqwhufE0oqDK0K47KOZYnCB
Rsz8pR3SY+jrByWa4iVtEVbyGlGhtj4cVzLpeyyqG8yXLYktscleNShNfPplMT73
cN1uCzxYr6guJMB+Tnh9Cp6PQkMCAwEAAaOCAVkwggFVMAkGA1UdEwQCMAAwLQYJ
YIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
HQ4EFgQU3kLvLZijbKiq4IxxLJ1kI6nifoEwgdcGA1UdIwSBzzCBzIAUZu7DFz09
q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
hCcEpzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcN
AQEFBQADgYEAHEQm6uFmJcvkjlcc9rkXImJAEpCPO7JhelSUj7EgC7+jUeP6HKG+
kjrQdkTAV4OrauQaRUmkrzkNYDL8Or7X+12Zeh+H59WrhKJekNi/+oltMiYCXjE1
aH8x9WtRRryvcO1aCX3sskhP/sUvVgSt9sHSKuRqxIf+CDXFOMteSsQ=
-----END CERTIFICATE-----

View file

@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMrJBdAJTj6k/NUU
9KXoNNNrUePzYuqh8OjtxCq88E/KB9/jiPr0IZk1Dj3qsIbnxNKKgytCuOyjmWJw
gUbM/KUd0mPo6wclmuIlbRFW8hpRobY+HFcy6XssqhvMly2JLbHJXjUoTXz6ZTE+
93Ddbgs8WK+oLiTAfk54fQqej0JDAgMBAAECgYBOFEzKp2qbMEexe9ofL2N3rDDh
xkrl8OijpzkLA6i78BxMFn4dsnZlWUpciMrjhsYAExkiRRSS+QMMJimAq1jzQqc3
FAQV2XGYwkd0cUn7iZGvfNnEPysjsfyYQM+m+sT0ATj4BZjVShC6kkSjTdm1leLN
OSvcHdcu3Xxg9ufF0QJBAPYdnNt5sIndt2WECePuRVi+uF4mlxTobFY0fjn26yhC
4RsnhhD3Vldygo9gvnkwrAZYaALGSPBewes2InxvjA8CQQDS7erKiNXpwoqz5XiU
SVEsIIVTdWzBjGbIqMOu/hUwM5FK4j6JTBks0aTGMyh0YV9L1EzM0X79J29JahCe
iQKNAkBKNMOGqTpBV0hko1sYDk96YobUXG5RL4L6uvkUIQ7mJMQam+AgXXL7Ctuy
v0iu4a38e8tgisiTMP7nHHtpaXihAkAOiN54/lzfMsykANgCP9scE1GcoqbP34Dl
qttxH4kOPT9xzY1JoLjLYdbc4YGUI3GRpBt2sajygNkmUey7P+2xAkBBsVCZFvTw
qHvOpPS2kX5ml5xoc/QAHK9N7kR+X7XFYx82RTVSqJEK4lPb+aEWn+CjiIewO4Q5
ksDFuNxAzbhl
-----END PRIVATE KEY-----

View file

@ -0,0 +1,76 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4 (0x4)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
Validity
Not Before: Dec 4 15:01:20 2013 GMT
Not After : Dec 2 15:01:20 2023 GMT
Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=*/name=changeme/emailAddress=mail@host.domain
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (1024 bit)
Modulus:
00:c1:ff:7d:30:6f:64:4a:b1:92:b1:71:d1:c1:74:
e2:1d:db:2d:11:24:e1:00:d4:00:ae:6f:c8:9e:ae:
67:b3:4a:bd:f7:e6:9e:57:6d:19:4c:3c:23:94:2d:
3d:d6:63:84:d8:fa:76:2b:38:12:c1:ed:20:9d:32:
e0:e8:c2:bf:9a:77:70:04:3f:7f:ca:8c:2c:82:d6:
3d:25:5c:02:1a:4f:64:93:03:dd:9c:42:97:5e:09:
49:af:f0:c2:e1:30:08:0e:21:46:95:d1:13:59:c0:
c8:76:be:94:0d:8b:43:67:21:33:b2:08:60:9d:76:
a8:05:32:1e:f9:95:09:14:75
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Cert Type:
SSL Server
Netscape Comment:
Easy-RSA Generated Server Certificate
X509v3 Subject Key Identifier:
14:02:FD:FD:DD:13:38:E0:71:EA:D1:BE:C0:0E:89:1A:2D:B6:19:06
X509v3 Authority Key Identifier:
keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
serial:FD:AB:EC:6A:84:27:04:A7
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Key Usage:
Digital Signature, Key Encipherment
Signature Algorithm: sha1WithRSAEncryption
40:0f:10:39:c4:b7:0f:0d:2f:bf:d2:16:cc:8e:d3:9a:fb:8b:
ce:4b:7b:0d:48:77:ce:f1:fe:d5:8f:ea:b1:71:ed:49:1d:9f:
23:3a:16:d4:70:7c:c5:29:bf:e4:90:34:d0:f0:00:24:f4:e4:
df:2c:c3:83:01:66:61:c9:a8:ab:29:e7:98:6d:27:89:4a:76:
c9:2e:19:8e:fe:6e:d5:f8:99:11:0e:97:67:4b:34:e3:1e:e3:
9f:35:00:a5:32:f9:b5:2c:f2:e0:c5:2e:cc:81:bd:18:dd:5c:
12:c8:6b:fa:0c:17:74:30:55:f6:6e:20:9a:6c:1e:09:b4:0c:
15:42
-----BEGIN CERTIFICATE-----
MIIEKjCCA5OgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
bWFpbjAeFw0xMzEyMDQxNTAxMjBaFw0yMzEyMDIxNTAxMjBaMIGbMQswCQYDVQQG
EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEKMAgGA1UEAxQBKjER
MA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21h
aW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMH/fTBvZEqxkrFx0cF04h3b
LREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y4OjCv5p3
cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+lA2LQ2ch
M7IIYJ12qAUyHvmVCRR1AgMBAAGjggFzMIIBbzAJBgNVHRMEAjAAMBEGCWCGSAGG
+EIBAQQEAwIGQDA0BglghkgBhvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNl
cnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUFAL9/d0TOOBx6tG+wA6JGi22GQYw
gdcGA1UdIwSBzzCBzIAUZu7DFz09q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJ
BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUw
EwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD
EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h
aWxAaG9zdC5kb21haW6CCQD9q+xqhCcEpzATBgNVHSUEDDAKBggrBgEFBQcDATAL
BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAQA8QOcS3Dw0vv9IWzI7TmvuL
zkt7DUh3zvH+1Y/qsXHtSR2fIzoW1HB8xSm/5JA00PAAJPTk3yzDgwFmYcmoqynn
mG0niUp2yS4Zjv5u1fiZEQ6XZ0s04x7jnzUApTL5tSzy4MUuzIG9GN1cEshr+gwX
dDBV9m4gmmweCbQMFUI=
-----END CERTIFICATE-----

View file

@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMH/fTBvZEqxkrFx
0cF04h3bLREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y
4OjCv5p3cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+
lA2LQ2chM7IIYJ12qAUyHvmVCRR1AgMBAAECgYAmwckb9RUfSwyYgLm8IYLPHiuJ
wkllZfVg5Bo7gXJcQnFjZmJ56uTj8xvUjZlODIHM63TSO5ibv6kFXtXKCqZGd2M+
wGbhZ0f+2GvKcwMmJERnIQjuoNaYSQLT0tM0VB9Iz0rJlZC+tzPZ+5pPqEumRdsS
IzWNXfF42AhcbwAQYQJBAPVXtMYIJc9EZsz86ZcQiMPWUpCX5vnRmtwL8kKyR8D5
4KfYeiowyFffSRMMcclwNHq7TgSXN+nIXM9WyzyzwikCQQDKbNA28AgZp9aT54HP
WnbeE2pmt+uk/zl/BtxJSoK6H+69Jec+lf7EgL7HgOWYRSNot4uQWu8IhsHLTiUq
+0FtAkEAqwlRxRy4/x24bP+D+QRV0/D97j93joFJbE4Hved7jlSlAV4xDGilwlyv
HNB4Iu5OJ6Gcaibhm+FKkmD3noHSwQJBAIpu3fokLzX0bS+bDFBU6qO3HXX/47xj
+tsfQvkwZrSI8AkU6c8IX0HdVhsz0FBRQAT2ORDQz1XCarfxykNZrwUCQQCGCBIc
BBCWzhHlswlGidWJg3HqqO6hPPClEr3B5G87oCsdeYwiO23XT6rUnoJXfJHp6oCW
5nCwDu5ZTP+khltg
-----END PRIVATE KEY-----

View file

@ -0,0 +1,33 @@
package volume // import "github.com/docker/docker/integration/volume"
import (
"fmt"
"os"
"testing"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
err = environment.EnsureFrozenImagesLinux(testEnv)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectAll(t, testEnv)
return func() { testEnv.Clean(t) }
}

View file

@ -0,0 +1,116 @@
package volume
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/google/go-cmp/cmp/cmpopts"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/skip"
)
func TestVolumesCreateAndList(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
name := t.Name()
vol, err := client.VolumeCreate(ctx, volumetypes.VolumeCreateBody{
Name: name,
})
assert.NilError(t, err)
expected := types.Volume{
// Ignore timestamp of CreatedAt
CreatedAt: vol.CreatedAt,
Driver: "local",
Scope: "local",
Name: name,
Mountpoint: fmt.Sprintf("%s/volumes/%s/_data", testEnv.DaemonInfo.DockerRootDir, name),
}
assert.Check(t, is.DeepEqual(vol, expected, cmpopts.EquateEmpty()))
volumes, err := client.VolumeList(ctx, filters.Args{})
assert.NilError(t, err)
assert.Check(t, is.Equal(len(volumes.Volumes), 1))
assert.Check(t, volumes.Volumes[0] != nil)
assert.Check(t, is.DeepEqual(*volumes.Volumes[0], expected, cmpopts.EquateEmpty()))
}
func TestVolumesRemove(t *testing.T) {
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
prefix, slash := getPrefixAndSlashFromDaemonPlatform()
id := container.Create(t, ctx, client, container.WithVolume(prefix+slash+"foo"))
c, err := client.ContainerInspect(ctx, id)
assert.NilError(t, err)
vname := c.Mounts[0].Name
err = client.VolumeRemove(ctx, vname, false)
assert.Check(t, is.ErrorContains(err, "volume is in use"))
err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
Force: true,
})
assert.NilError(t, err)
err = client.VolumeRemove(ctx, vname, false)
assert.NilError(t, err)
}
func TestVolumesInspect(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
defer setupTest(t)()
client := request.NewAPIClient(t)
ctx := context.Background()
// sampling current time minus a minute so to now have false positive in case of delays
now := time.Now().Truncate(time.Minute)
name := t.Name()
_, err := client.VolumeCreate(ctx, volumetypes.VolumeCreateBody{
Name: name,
})
assert.NilError(t, err)
vol, err := client.VolumeInspect(ctx, name)
assert.NilError(t, err)
expected := types.Volume{
// Ignore timestamp of CreatedAt
CreatedAt: vol.CreatedAt,
Driver: "local",
Scope: "local",
Name: name,
Mountpoint: fmt.Sprintf("%s/volumes/%s/_data", testEnv.DaemonInfo.DockerRootDir, name),
}
assert.Check(t, is.DeepEqual(vol, expected, cmpopts.EquateEmpty()))
// comparing CreatedAt field time for the new volume to now. Removing a minute from both to avoid false positive
testCreatedAt, err := time.Parse(time.RFC3339, strings.TrimSpace(vol.CreatedAt))
assert.NilError(t, err)
testCreatedAt = testCreatedAt.Truncate(time.Minute)
assert.Check(t, is.Equal(testCreatedAt.Equal(now), true), "Time Volume is CreatedAt not equal to current time")
}
func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
if testEnv.OSType == "windows" {
return "c:", `\`
}
return "", "/"
}