Merge component 'engine' from git@github.com:moby/moby master

This commit is contained in:
GordonTheTurtle
2018-03-06 17:08:52 +00:00
41 changed files with 420 additions and 213 deletions

View File

@ -18,6 +18,7 @@ import (
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
containerpkg "github.com/docker/docker/container"
"github.com/docker/docker/daemon"
"github.com/docker/docker/daemon/cluster/convert"
executorpkg "github.com/docker/docker/daemon/cluster/executor"
"github.com/docker/libnetwork"
@ -155,7 +156,11 @@ func (c *containerAdapter) createNetworks(ctx context.Context) error {
if _, ok := err.(libnetwork.NetworkNameError); ok {
continue
}
// We will continue if CreateManagedNetwork returns PredefinedNetworkError error.
// Other callers still can treat it as Error.
if _, ok := err.(daemon.PredefinedNetworkError); ok {
continue
}
return err
}
}

View File

@ -6,30 +6,8 @@ import (
"bytes"
"fmt"
"os"
"path/filepath"
"golang.org/x/sys/unix"
)
// FIXME: this is copy-pasted from the aufs driver.
// It should be moved into the core.
// Mounted returns true if a mount point exists.
func Mounted(mountpoint string) (bool, error) {
var mntpointSt unix.Stat_t
if err := unix.Stat(mountpoint, &mntpointSt); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
var parentSt unix.Stat_t
if err := unix.Stat(filepath.Join(mountpoint, ".."), &parentSt); err != nil {
return false, err
}
return mntpointSt.Dev != parentSt.Dev, nil
}
type probeData struct {
fsName string
magic string

View File

@ -24,6 +24,16 @@ import (
"golang.org/x/net/context"
)
// PredefinedNetworkError is returned when user tries to create predefined network that already exists.
type PredefinedNetworkError string
func (pnr PredefinedNetworkError) Error() string {
return fmt.Sprintf("operation is not permitted on predefined %s network ", string(pnr))
}
// Forbidden denotes the type of this error
func (pnr PredefinedNetworkError) Forbidden() {}
// NetworkControllerEnabled checks if the networking stack is enabled.
// This feature depends on OS primitives and it's disabled in systems like Windows.
func (daemon *Daemon) NetworkControllerEnabled() bool {
@ -267,9 +277,8 @@ func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.N
}
func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string, agent bool) (*types.NetworkCreateResponse, error) {
if runconfig.IsPreDefinedNetwork(create.Name) && !agent {
err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name)
return nil, errdefs.Forbidden(err)
if runconfig.IsPreDefinedNetwork(create.Name) {
return nil, PredefinedNetworkError(create.Name)
}
var warning string

View File

@ -19,6 +19,7 @@ import (
"github.com/docker/docker/registry"
"github.com/docker/libtrust"
"github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/net/context"
)
@ -86,7 +87,8 @@ type ImagePushConfig struct {
type ImageConfigStore interface {
Put([]byte) (digest.Digest, error)
Get(digest.Digest) ([]byte, error)
RootFSAndOSFromConfig([]byte) (*image.RootFS, string, error)
RootFSFromConfig([]byte) (*image.RootFS, error)
PlatformFromConfig([]byte) (*specs.Platform, error)
}
// PushLayerProvider provides layers to be pushed by ChainID.
@ -140,18 +142,26 @@ func (s *imageConfigStore) Get(d digest.Digest) ([]byte, error) {
return img.RawJSON(), nil
}
func (s *imageConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
func (s *imageConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
var unmarshalledConfig image.Image
if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
return nil, "", err
return nil, err
}
return unmarshalledConfig.RootFS, nil
}
func (s *imageConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) {
var unmarshalledConfig image.Image
if err := json.Unmarshal(c, &unmarshalledConfig); err != nil {
return nil, err
}
// fail immediately on Windows when downloading a non-Windows image
// and vice versa. Exception on Windows if Linux Containers are enabled.
if runtime.GOOS == "windows" && unmarshalledConfig.OS == "linux" && !system.LCOWSupported() {
return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
} else if runtime.GOOS != "windows" && unmarshalledConfig.OS == "windows" {
return nil, "", fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
return nil, fmt.Errorf("image operating system %q cannot be used on this platform", unmarshalledConfig.OS)
}
os := unmarshalledConfig.OS
@ -159,9 +169,9 @@ func (s *imageConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, strin
os = runtime.GOOS
}
if !system.IsOSSupported(os) {
return nil, "", system.ErrNotSupportedOperatingSystem
return nil, system.ErrNotSupportedOperatingSystem
}
return unmarshalledConfig.RootFS, os, nil
return &specs.Platform{OS: os, OSVersion: unmarshalledConfig.OSVersion}, nil
}
type storeLayerProvider struct {

View File

@ -30,6 +30,7 @@ import (
refstore "github.com/docker/docker/reference"
"github.com/docker/docker/registry"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
@ -584,11 +585,11 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
}()
var (
configJSON []byte // raw serialized image config
downloadedRootFS *image.RootFS // rootFS from registered layers
configRootFS *image.RootFS // rootFS from configuration
release func() // release resources from rootFS download
configOS string // for LCOW when registering downloaded layers
configJSON []byte // raw serialized image config
downloadedRootFS *image.RootFS // rootFS from registered layers
configRootFS *image.RootFS // rootFS from configuration
release func() // release resources from rootFS download
configPlatform *specs.Platform // for LCOW when registering downloaded layers
)
// https://github.com/docker/docker/issues/24766 - Err on the side of caution,
@ -600,14 +601,16 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
// check to block Windows images being pulled on Linux is implemented, it
// may be necessary to perform the same type of serialisation.
if runtime.GOOS == "windows" {
configJSON, configRootFS, configOS, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
configJSON, configRootFS, configPlatform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
if err != nil {
return "", "", err
}
if configRootFS == nil {
return "", "", errRootFSInvalid
}
if err := checkImageCompatibility(configPlatform.OS, configPlatform.OSVersion); err != nil {
return "", "", err
}
if len(descriptors) != len(configRootFS.DiffIDs) {
return "", "", errRootFSMismatch
@ -615,8 +618,8 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
// Early bath if the requested OS doesn't match that of the configuration.
// This avoids doing the download, only to potentially fail later.
if !strings.EqualFold(configOS, requestedOS) {
return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configOS, requestedOS)
if !strings.EqualFold(configPlatform.OS, requestedOS) {
return "", "", fmt.Errorf("cannot download image with operating system %q when requesting %q", configPlatform.OS, requestedOS)
}
// Populate diff ids in descriptors to avoid downloading foreign layers
@ -698,16 +701,20 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
return imageID, manifestDigest, nil
}
func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, string, error) {
func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, *specs.Platform, error) {
select {
case configJSON := <-configChan:
rootfs, os, err := s.RootFSAndOSFromConfig(configJSON)
rootfs, err := s.RootFSFromConfig(configJSON)
if err != nil {
return nil, nil, "", err
return nil, nil, nil, err
}
return configJSON, rootfs, os, nil
platform, err := s.PlatformFromConfig(configJSON)
if err != nil {
return nil, nil, nil, err
}
return configJSON, rootfs, platform, nil
case err := <-errChan:
return nil, nil, "", err
return nil, nil, nil, err
// Don't need a case for ctx.Done in the select because cancellation
// will trigger an error in p.pullSchema2ImageConfig.
}
@ -736,6 +743,10 @@ func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mf
}
manifestDigest := manifestMatches[0].Digest
if err := checkImageCompatibility(manifestMatches[0].Platform.OS, manifestMatches[0].Platform.OSVersion); err != nil {
return "", "", err
}
manSvc, err := p.repo.Manifests(ctx)
if err != nil {
return "", "", err

View File

@ -27,3 +27,8 @@ func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []m
}
return matches
}
// checkImageCompatibility is a Windows-specific function. No-op on Linux
func checkImageCompatibility(imageOS, imageOSVersion string) error {
return nil
}

View File

@ -1,11 +1,13 @@
package distribution // import "github.com/docker/docker/distribution"
import (
"errors"
"fmt"
"net/http"
"os"
"runtime"
"sort"
"strconv"
"strings"
"github.com/docker/distribution"
@ -63,7 +65,6 @@ func (ld *v2LayerDescriptor) open(ctx context.Context) (distribution.ReadSeekClo
func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
osVersion := ""
if os == "windows" {
// TODO: Add UBR (Update Build Release) component after build
version := system.GetOSVersion()
osVersion = fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build)
logrus.Debugf("will prefer entries with version %s", osVersion)
@ -71,10 +72,11 @@ func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []m
var matches []manifestlist.ManifestDescriptor
for _, manifestDescriptor := range manifests {
// TODO: Consider filtering out greater versions, including only greater UBR
if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
matches = append(matches, manifestDescriptor)
logrus.Debugf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
logrus.Debugf("found match for %s/%s %s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
} else {
logrus.Debugf("ignoring %s/%s %s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.Platform.OSVersion, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
}
}
if os == "windows" {
@ -107,3 +109,22 @@ func (mbv manifestsByVersion) Len() int {
func (mbv manifestsByVersion) Swap(i, j int) {
mbv.list[i], mbv.list[j] = mbv.list[j], mbv.list[i]
}
// checkImageCompatibility blocks pulling incompatible images based on a later OS build
// Fixes https://github.com/moby/moby/issues/36184.
func checkImageCompatibility(imageOS, imageOSVersion string) error {
if imageOS == "windows" {
hostOSV := system.GetOSVersion()
splitImageOSVersion := strings.Split(imageOSVersion, ".") // eg 10.0.16299.nnnn
if len(splitImageOSVersion) >= 3 {
if imageOSBuild, err := strconv.Atoi(splitImageOSVersion[2]); err == nil {
if imageOSBuild > int(hostOSV.Build) {
errMsg := fmt.Sprintf("a Windows version %s.%s.%s-based image is incompatible with a %s host", splitImageOSVersion[0], splitImageOSVersion[1], splitImageOSVersion[2], hostOSV.ToString())
logrus.Debugf(errMsg)
return errors.New(errMsg)
}
}
}
}
return nil
}

View File

@ -118,12 +118,17 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, id
return fmt.Errorf("could not find image from tag %s: %v", reference.FamiliarString(ref), err)
}
rootfs, os, err := p.config.ImageStore.RootFSAndOSFromConfig(imgConfig)
rootfs, err := p.config.ImageStore.RootFSFromConfig(imgConfig)
if err != nil {
return fmt.Errorf("unable to get rootfs for image %s: %s", reference.FamiliarString(ref), err)
}
l, err := p.config.LayerStores[os].Get(rootfs.ChainID())
platform, err := p.config.ImageStore.PlatformFromConfig(imgConfig)
if err != nil {
return fmt.Errorf("unable to get platform for image %s: %s", reference.FamiliarString(ref), err)
}
l, err := p.config.LayerStores[platform.OS].Get(rootfs.ChainID())
if err != nil {
return fmt.Errorf("failed to get top layer from image: %v", err)
}

View File

@ -85,7 +85,7 @@ type DockerSuite struct {
}
func (s *DockerSuite) OnTimeout(c *check.C) {
if !testEnv.IsLocalDaemon() {
if testEnv.IsRemoteDaemon() {
return
}
path := filepath.Join(os.Getenv("DEST"), "docker.pid")

View File

@ -1713,7 +1713,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsValidation(c *check.C) {
Type: "bind",
Source: notExistPath,
Target: destPath}}},
msg: "bind source path does not exist",
msg: "bind mount source path does not exist: " + notExistPath,
},
{
config: containertypes.Config{

View File

@ -1,74 +0,0 @@
package main
import (
"encoding/json"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/request"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/go-check/check"
)
func (s *DockerSuite) TestEventsAPIEmptyOutput(c *check.C) {
type apiResp struct {
resp *http.Response
err error
}
chResp := make(chan *apiResp)
go func() {
resp, body, err := request.Get("/events")
body.Close()
chResp <- &apiResp{resp, err}
}()
select {
case r := <-chResp:
c.Assert(r.err, checker.IsNil)
c.Assert(r.resp.StatusCode, checker.Equals, http.StatusOK)
case <-time.After(3 * time.Second):
c.Fatal("timeout waiting for events api to respond, should have responded immediately")
}
}
func (s *DockerSuite) TestEventsAPIBackwardsCompatible(c *check.C) {
since := daemonTime(c).Unix()
ts := strconv.FormatInt(since, 10)
out := runSleepingContainer(c, "--name=foo", "-d")
containerID := strings.TrimSpace(out)
c.Assert(waitRun(containerID), checker.IsNil)
q := url.Values{}
q.Set("since", ts)
_, body, err := request.Get("/events?" + q.Encode())
c.Assert(err, checker.IsNil)
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
}
c.Fatal(err)
}
if event.Status == "create" && event.ID == containerID {
containerCreateEvent = &event
break
}
}
c.Assert(containerCreateEvent, checker.Not(checker.IsNil))
c.Assert(containerCreateEvent.Status, checker.Equals, "create")
c.Assert(containerCreateEvent.ID, checker.Equals, containerID)
c.Assert(containerCreateEvent.From, checker.Equals, "busybox")
}

View File

@ -1,32 +0,0 @@
package main
import (
"github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/go-check/check"
)
func (s *DockerSuite) TestRmContainerOrphaning(c *check.C) {
dockerfile1 := `FROM busybox:latest
ENTRYPOINT ["true"]`
img := "test-container-orphaning"
dockerfile2 := `FROM busybox:latest
ENTRYPOINT ["true"]
MAINTAINER Integration Tests`
// build first dockerfile
buildImageSuccessfully(c, img, build.WithDockerfile(dockerfile1))
img1 := getIDByName(c, img)
// run container on first image
dockerCmd(c, "run", img)
// rebuild dockerfile with a small addition at the end
buildImageSuccessfully(c, img, build.WithDockerfile(dockerfile2))
// try to remove the image, should not error out.
out, _, err := dockerCmdWithError("rmi", img)
c.Assert(err, check.IsNil, check.Commentf("Expected to removing the image, but failed: %s", out))
// check if we deleted the first image
out, _ = dockerCmd(c, "images", "-q", "--no-trunc")
c.Assert(out, checker.Contains, img1, check.Commentf("Orphaned container (could not find %q in docker images): %s", img1, out))
}

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/integration/internal/container"
"github.com/gotestyourself/gotestyourself/skip"
"github.com/stretchr/testify/assert"
"golang.org/x/sys/unix"
)
@ -26,6 +27,7 @@ import (
// 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, "", "dockerd", daemon.Config{})

View File

@ -40,9 +40,9 @@ func TestInspectCpusetInConfigPre120(t *testing.T) {
require.NoError(t, err, "unable to unmarshal body for version 1.19: %s", err)
config, ok := inspectJSON["Config"]
assert.Equal(t, ok, true, "Unable to find 'Config'")
assert.Equal(t, true, ok, "Unable to find 'Config'")
cfg := config.(map[string]interface{})
_, ok = cfg["Cpuset"]
assert.Equal(t, ok, true, "API version 1.19 expected to include Cpuset in 'Config'")
assert.Equal(t, true, ok, "API version 1.19 expected to include Cpuset in 'Config'")
}

View File

@ -163,7 +163,7 @@ func TestInspectOomKilledTrue(t *testing.T) {
inspect, err := client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.State.OOMKilled, true)
assert.Equal(t, true, inspect.State.OOMKilled)
}
func TestInspectOomKilledFalse(t *testing.T) {
@ -179,5 +179,5 @@ func TestInspectOomKilledFalse(t *testing.T) {
inspect, err := client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.State.OOMKilled, false)
assert.Equal(t, false, inspect.State.OOMKilled)
}

View File

@ -20,7 +20,7 @@ import (
)
func TestLinksEtcHostsContentMatch(t *testing.T) {
skip.If(t, !testEnv.IsLocalDaemon())
skip.If(t, testEnv.IsRemoteDaemon())
hosts, err := ioutil.ReadFile("/etc/hosts")
skip.If(t, os.IsNotExist(err))

View File

@ -23,6 +23,7 @@ import (
)
func TestContainerShmNoLeak(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon(), "cannot start daemon on remote test run")
t.Parallel()
d := daemon.New(t, "docker", "dockerd", daemon.Config{})
client, err := d.NewClient()
@ -94,7 +95,7 @@ func TestContainerShmNoLeak(t *testing.T) {
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.IsLocalDaemon())
skip.If(t, testEnv.DaemonInfo.OSType != "linux" || testEnv.IsRemoteDaemon())
defer setupTest(t)()

View File

@ -22,7 +22,7 @@ import (
)
func TestNetworkNat(t *testing.T) {
skip.If(t, !testEnv.IsLocalDaemon())
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
@ -40,7 +40,7 @@ func TestNetworkNat(t *testing.T) {
}
func TestNetworkLocalhostTCPNat(t *testing.T) {
skip.If(t, !testEnv.IsLocalDaemon())
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
@ -57,7 +57,7 @@ func TestNetworkLocalhostTCPNat(t *testing.T) {
}
func TestNetworkLoopbackNat(t *testing.T) {
skip.If(t, !testEnv.IsLocalDaemon())
skip.If(t, testEnv.IsRemoteDaemon())
msg := "it works"
startServerContainer(t, msg, 8080)

View File

@ -25,20 +25,19 @@ func TestPause(t *testing.T) {
client := request.NewAPIClient(t)
ctx := context.Background()
name := "testeventpause"
cID := container.Run(t, ctx, client, container.WithName(name))
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, name)
err := client.ContainerPause(ctx, cID)
require.NoError(t, err)
inspect, err := client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.State.Paused, true)
assert.Equal(t, true, inspect.State.Paused)
err = client.ContainerUnpause(ctx, name)
err = client.ContainerUnpause(ctx, cID)
require.NoError(t, err)
until := request.DaemonUnixTime(ctx, t, client, testEnv)
@ -46,9 +45,9 @@ func TestPause(t *testing.T) {
messages, errs := client.Events(ctx, types.EventsOptions{
Since: since,
Until: until,
Filters: filters.NewArgs(filters.Arg("container", name)),
Filters: filters.NewArgs(filters.Arg("container", cID)),
})
assert.Equal(t, getEventActions(t, messages, errs), []string{"pause", "unpause"})
assert.Equal(t, []string{"pause", "unpause"}, getEventActions(t, messages, errs))
}
func TestPauseFailsOnWindowsServerContainers(t *testing.T) {
@ -89,7 +88,7 @@ func getEventActions(t *testing.T, messages <-chan events.Message, errs <-chan e
for {
select {
case err := <-errs:
assert.Equal(t, err == nil || err == io.EOF, true)
assert.True(t, err == nil || err == io.EOF)
return actions
case e := <-messages:
actions = append(actions, e.Status)

View File

@ -27,7 +27,7 @@ func getPrefixAndSlashFromDaemonPlatform() (prefix, slash string) {
// Test case for #5244: `docker rm` fails if bind dir doesn't exist anymore
func TestRemoveContainerWithRemovedVolume(t *testing.T) {
skip.If(t, !testEnv.IsLocalDaemon())
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
ctx := context.Background()
@ -66,7 +66,7 @@ func TestRemoveContainerWithVolume(t *testing.T) {
insp, _, err := client.ContainerInspectWithRaw(ctx, cID, true)
require.NoError(t, err)
assert.Equal(t, len(insp.Mounts), 1)
assert.Equal(t, 1, len(insp.Mounts))
volName := insp.Mounts[0].Name
err = client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{
@ -76,7 +76,7 @@ func TestRemoveContainerWithVolume(t *testing.T) {
volumes, err := client.VolumeList(ctx, filters.NewArgs(filters.Arg("name", volName)))
require.NoError(t, err)
assert.Equal(t, len(volumes.Volumes), 0)
assert.Equal(t, 0, len(volumes.Volumes))
}
func TestRemoveContainerRunning(t *testing.T) {

View File

@ -55,7 +55,7 @@ func TestRenameStoppedContainer(t *testing.T) {
inspect, err := client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.Name, "/"+oldName)
assert.Equal(t, "/"+oldName, inspect.Name)
newName := "new_name" + stringid.GenerateNonCryptoID()
err = client.ContainerRename(ctx, oldName, newName)
@ -63,7 +63,7 @@ func TestRenameStoppedContainer(t *testing.T) {
inspect, err = client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.Name, "/"+newName)
assert.Equal(t, "/"+newName, inspect.Name)
}
func TestRenameRunningContainerAndReuse(t *testing.T) {
@ -81,7 +81,7 @@ func TestRenameRunningContainerAndReuse(t *testing.T) {
inspect, err := client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.Name, "/"+newName)
assert.Equal(t, "/"+newName, inspect.Name)
_, err = client.ContainerInspect(ctx, oldName)
testutil.ErrorContains(t, err, "No such container: "+oldName)
@ -91,7 +91,7 @@ func TestRenameRunningContainerAndReuse(t *testing.T) {
inspect, err = client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.Name, "/"+oldName)
assert.Equal(t, "/"+oldName, inspect.Name)
}
func TestRenameInvalidName(t *testing.T) {
@ -108,7 +108,7 @@ func TestRenameInvalidName(t *testing.T) {
inspect, err := client.ContainerInspect(ctx, oldName)
require.NoError(t, err)
assert.Equal(t, inspect.ID, cID)
assert.Equal(t, cID, inspect.ID)
}
// Test case for GitHub issue 22466
@ -133,6 +133,10 @@ func TestRenameAnonymousContainer(t *testing.T) {
})
err = client.ContainerRename(ctx, cID, "container1")
require.NoError(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, "container1", nil)
require.NoError(t, err)
err = client.ContainerStart(ctx, "container1", types.ContainerStartOptions{})
require.NoError(t, err)
@ -152,7 +156,7 @@ func TestRenameAnonymousContainer(t *testing.T) {
inspect, err := client.ContainerInspect(ctx, cID)
require.NoError(t, err)
assert.Equal(t, inspect.State.ExitCode, 0)
assert.Equal(t, 0, inspect.State.ExitCode, "container %s exited with the wrong exitcode: %+v", cID, inspect)
}
// TODO: should be a unit test
@ -175,7 +179,7 @@ func TestRenameContainerWithSameName(t *testing.T) {
// 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.IsLocalDaemon())
skip.If(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
ctx := context.Background()
@ -192,5 +196,5 @@ func TestRenameContainerWithLinkedContainer(t *testing.T) {
inspect, err := client.ContainerInspect(ctx, "app2/mysql")
require.NoError(t, err)
assert.Equal(t, inspect.ID, db1ID)
assert.Equal(t, db1ID, inspect.ID)
}

View File

@ -44,7 +44,7 @@ func TestResizeWithInvalidSize(t *testing.T) {
endpoint := "/containers/" + cID + "/resize?h=foo&w=bar"
res, _, err := req.Post(endpoint)
require.NoError(t, err)
assert.Equal(t, res.StatusCode, http.StatusBadRequest)
assert.Equal(t, http.StatusBadRequest, res.StatusCode)
}
func TestResizeWhenContainerNotStarted(t *testing.T) {

View File

@ -9,9 +9,11 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration-cli/daemon"
"github.com/gotestyourself/gotestyourself/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

View File

@ -0,0 +1,60 @@
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/integration/internal/request"
"github.com/docker/docker/internal/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
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,
})
require.NoError(t, err)
// verifies that reference now points to first image
resp, _, err := client.ImageInspectWithRaw(ctx, img)
require.NoError(t, err)
assert.Equal(t, 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,
})
require.NoError(t, err)
// verifies that reference now points to second image
resp, _, err = client.ImageInspectWithRaw(ctx, img)
require.NoError(t, err)
assert.Equal(t, resp.ID, commitResp2.ID)
// try to remove the image, should not error out.
_, err = client.ImageRemove(ctx, img, types.ImageRemoveOptions{})
require.NoError(t, err)
// check if the first image is still there
resp, _, err = client.ImageInspectWithRaw(ctx, commitResp1.ID)
require.NoError(t, err)
assert.Equal(t, resp.ID, commitResp1.ID)
// check if the second image has been deleted
_, _, err = client.ImageInspectWithRaw(ctx, commitResp2.ID)
testutil.ErrorContains(t, err, "No such image:")
}

View File

@ -20,8 +20,8 @@ func NewAPIClient(t *testing.T, ops ...func(*client.Client) error) client.APICli
return clt
}
// daemonTime provides the current time on the daemon host
func daemonTime(ctx context.Context, t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time {
// DaemonTime provides the current time on the daemon host
func DaemonTime(ctx context.Context, t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time {
if testEnv.IsLocalDaemon() {
return time.Now()
}
@ -37,6 +37,6 @@ func daemonTime(ctx context.Context, t *testing.T, client client.APIClient, test
// DaemonUnixTime returns the current time on the daemon host with nanoseconds precision.
// It return the time formatted how the client sends timestamps to the server.
func DaemonUnixTime(ctx context.Context, t *testing.T, client client.APIClient, testEnv *environment.Execution) string {
dt := daemonTime(ctx, t, client, testEnv)
dt := DaemonTime(ctx, t, client, testEnv)
return fmt.Sprintf("%d.%09d", dt.Unix(), int64(dt.Nanosecond()))
}

View File

@ -0,0 +1,70 @@
package network // import "github.com/docker/docker/integration/network"
import (
"runtime"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/gotestyourself/gotestyourself/poll"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestServiceWithPredefinedNetwork(t *testing.T) {
defer setupTest(t)()
d := newSwarm(t)
defer d.Stop(t)
client, err := client.NewClientWithOpts(client.WithHost((d.Sock())))
require.NoError(t, err)
hostName := "host"
var instances uint64 = 1
serviceName := "TestService"
serviceSpec := swarmServiceSpec(serviceName, instances)
serviceSpec.TaskTemplate.Networks = append(serviceSpec.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{Target: hostName})
serviceResp, err := client.ServiceCreate(context.Background(), serviceSpec, types.ServiceCreateOptions{
QueryRegistry: false,
})
require.NoError(t, err)
pollSettings := func(config *poll.Settings) {
if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
config.Timeout = 50 * time.Second
config.Delay = 100 * time.Millisecond
}
}
serviceID := serviceResp.ID
poll.WaitOn(t, serviceRunningCount(client, serviceID, instances), pollSettings)
_, _, err = client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
require.NoError(t, err)
err = client.ServiceRemove(context.Background(), serviceID)
require.NoError(t, err)
poll.WaitOn(t, serviceIsRemoved(client, serviceID), pollSettings)
poll.WaitOn(t, noTasks(client), pollSettings)
}
func serviceRunningCount(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)
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()
}
}

View File

@ -18,7 +18,7 @@ import (
)
func TestInspect(t *testing.T) {
skip.IfCondition(t, !testEnv.IsLocalDaemon())
skip.IfCondition(t, testEnv.IsRemoteDaemon())
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)

View File

@ -2,15 +2,22 @@ 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"
req "github.com/docker/docker/integration-cli/request"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/request"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -58,3 +65,55 @@ func TestEvents(t *testing.T) {
}
}
// 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")
require.NoError(t, err)
defer emptyBody.Close()
assert.Equal(t, http.StatusOK, emptyResp.StatusCode)
assert.True(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())
require.NoError(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.NotNil(t, containerCreateEvent)
assert.Equal(t, "create", containerCreateEvent.Status)
assert.Equal(t, cID, containerCreateEvent.ID)
assert.Equal(t, "busybox", containerCreateEvent.From)
}

View File

@ -96,7 +96,7 @@ func toSlash(path string) string {
}
// IsLocalDaemon is true if the daemon under test is on the same
// host as the CLI.
// host as the test process.
//
// Deterministically working out the environment in which CI is running
// to evaluate whether the daemon is local or remote is not possible through
@ -115,6 +115,12 @@ func (e *Execution) IsLocalDaemon() bool {
return os.Getenv("DOCKER_REMOTE_DAEMON") == ""
}
// IsRemoteDaemon is true if the daemon under test is on different host
// as the test process.
func (e *Execution) IsRemoteDaemon() bool {
return !e.IsLocalDaemon()
}
// Print the execution details to stdout
// TODO: print everything
func (e *Execution) Print() {

View File

@ -2,6 +2,7 @@ package plugins // import "github.com/docker/docker/pkg/plugins"
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
@ -13,7 +14,9 @@ import (
"github.com/docker/docker/pkg/plugins/transport"
"github.com/docker/go-connections/tlsconfig"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@ -232,3 +235,43 @@ func TestClientSendFile(t *testing.T) {
}
assert.Equal(t, m, output)
}
func TestClientWithRequestTimeout(t *testing.T) {
timeout := 1 * time.Millisecond
testHandler := func(w http.ResponseWriter, r *http.Request) {
time.Sleep(timeout + 1*time.Millisecond)
w.WriteHeader(http.StatusOK)
}
srv := httptest.NewServer(http.HandlerFunc(testHandler))
defer srv.Close()
client := &Client{http: srv.Client(), requestFactory: &testRequestWrapper{srv}}
_, err := client.callWithRetry("/Plugin.Hello", nil, false, WithRequestTimeout(timeout))
require.Error(t, err, "expected error")
err = errors.Cause(err)
switch e := err.(type) {
case *url.Error:
err = e.Err
}
require.Equal(t, context.DeadlineExceeded, err)
}
type testRequestWrapper struct {
*httptest.Server
}
func (w *testRequestWrapper) NewRequest(path string, data io.Reader) (*http.Request, error) {
req, err := http.NewRequest("POST", path, data)
if err != nil {
return nil, err
}
u, err := url.Parse(w.Server.URL)
if err != nil {
return nil, err
}
req.URL = u
return req, nil
}

View File

@ -1,6 +1,7 @@
package system // import "github.com/docker/docker/pkg/system"
import (
"fmt"
"unsafe"
"github.com/sirupsen/logrus"
@ -53,6 +54,10 @@ func GetOSVersion() OSVersion {
return osv
}
func (osv OSVersion) ToString() string {
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
}
// IsWindowsClient returns true if the SKU is client
// @engine maintainers - this function should not be removed or modified as it
// is used to enforce licensing restrictions on Windows.

View File

@ -33,6 +33,7 @@ import (
"github.com/docker/docker/plugin/v2"
refstore "github.com/docker/docker/reference"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
@ -146,10 +147,15 @@ func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) {
return s.config, nil
}
func (s *tempConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
func (s *tempConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
return configToRootFS(c)
}
func (s *tempConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) {
// TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS
return &specs.Platform{OS: runtime.GOOS}, nil
}
func computePrivileges(c types.PluginConfig) types.PluginPrivileges {
var privileges types.PluginPrivileges
if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" {
@ -534,10 +540,15 @@ func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) {
return ioutil.ReadAll(rwc)
}
func (s *pluginConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
func (s *pluginConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
return configToRootFS(c)
}
func (s *pluginConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) {
// TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS
return &specs.Platform{OS: runtime.GOOS}, nil
}
type pluginLayerProvider struct {
pm *Manager
plugin *v2.Plugin

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"github.com/docker/docker/distribution/xfer"
"github.com/docker/docker/image"
@ -14,6 +15,7 @@ import (
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/progress"
"github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
@ -178,6 +180,10 @@ func (dm *downloadManager) Put(dt []byte) (digest.Digest, error) {
func (dm *downloadManager) Get(d digest.Digest) ([]byte, error) {
return nil, fmt.Errorf("digest not found")
}
func (dm *downloadManager) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
func (dm *downloadManager) RootFSFromConfig(c []byte) (*image.RootFS, error) {
return configToRootFS(c)
}
func (dm *downloadManager) PlatformFromConfig(c []byte) (*specs.Platform, error) {
// TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS
return &specs.Platform{OS: runtime.GOOS}, nil
}

View File

@ -8,7 +8,6 @@ import (
"path/filepath"
"reflect"
"regexp"
"runtime"
"sort"
"strings"
"sync"
@ -353,19 +352,17 @@ func isEqualPrivilege(a, b types.PluginPrivilege) bool {
return reflect.DeepEqual(a.Value, b.Value)
}
func configToRootFS(c []byte) (*image.RootFS, string, error) {
// TODO @jhowardmsft LCOW - Will need to revisit this.
os := runtime.GOOS
func configToRootFS(c []byte) (*image.RootFS, error) {
var pluginConfig types.PluginConfig
if err := json.Unmarshal(c, &pluginConfig); err != nil {
return nil, "", err
return nil, err
}
// validation for empty rootfs is in distribution code
if pluginConfig.Rootfs == nil {
return nil, os, nil
return nil, nil
}
return rootFSFromPlugin(pluginConfig.Rootfs), os, nil
return rootFSFromPlugin(pluginConfig.Rootfs), nil
}
func rootFSFromPlugin(pluginfs *types.PluginConfigRootfs) *image.RootFS {

View File

@ -322,6 +322,7 @@
"stat64",
"statfs",
"statfs64",
"statx",
"symlink",
"symlinkat",
"sync",

View File

@ -315,6 +315,7 @@ func DefaultProfile() *types.Seccomp {
"stat64",
"statfs",
"statfs64",
"statx",
"symlink",
"symlinkat",
"sync",

View File

@ -83,7 +83,7 @@ func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSour
if validateBindSourceExists {
exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source)
if !exists {
return &errMountConfig{mnt, errBindNotExist}
return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
}
}

View File

@ -7,8 +7,6 @@ import (
"github.com/pkg/errors"
)
var errBindNotExist = errors.New("bind source path does not exist")
type errMountConfig struct {
mount *mount.Mount
err error
@ -18,6 +16,10 @@ func (e *errMountConfig) Error() string {
return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
}
func errBindSourceDoesNotExist(path string) error {
return errors.Errorf("bind mount source path does not exist: %s", path)
}
func errExtraField(name string) error {
return errors.Errorf("field %s must not be specified", name)
}

View File

@ -31,7 +31,7 @@ func TestValidateMount(t *testing.T) {
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil},
{mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")},
{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindNotExist},
{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindSourceDoesNotExist(testSourcePath)},
}
lcowCases := []struct {
@ -44,7 +44,7 @@ func TestValidateMount(t *testing.T) {
{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
{mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")},
{mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
{mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindNotExist},
{mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindSourceDoesNotExist("c:\\foo")},
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil},
{mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")},
}

View File

@ -120,7 +120,7 @@ func TestParseMountRaw(t *testing.T) {
`c:\:d:\:xyzzy`: "invalid volume specification: ",
`c:`: "cannot be `c:`",
`c:\`: "cannot be `c:`",
`c:\notexist:d:`: `source path does not exist`,
`c:\notexist:d:`: `bind mount source path does not exist: c:\notexist`,
`c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`,
`name<:d:`: `invalid volume specification`,
`name>:d:`: `invalid volume specification`,
@ -189,7 +189,7 @@ func TestParseMountRaw(t *testing.T) {
`c:\:/foo:xyzzy`: "invalid volume specification: ",
`/`: "destination can't be '/'",
`/..`: "destination can't be '/'",
`c:\notexist:/foo`: `source path does not exist`,
`c:\notexist:/foo`: `bind mount source path does not exist: c:\notexist`,
`c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`,
`name<:/foo`: `invalid volume specification`,
`name>:/foo`: `invalid volume specification`,

View File

@ -252,7 +252,7 @@ func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, destRegex strin
return &errMountConfig{mnt, err}
}
if !exists {
return &errMountConfig{mnt, errBindNotExist}
return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
}
if !isdir {
return &errMountConfig{mnt, fmt.Errorf("source path must be a directory")}