Refactor test environment
split all non-cli portions into a new internal/test/environment package Set a test environment on packages instead of creating new ones. Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
61e7d0595d
commit
f85ef42ea5
|
@ -2,10 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -20,6 +22,7 @@ import (
|
||||||
"github.com/docker/docker/integration-cli/environment"
|
"github.com/docker/docker/integration-cli/environment"
|
||||||
"github.com/docker/docker/integration-cli/fixtures/plugin"
|
"github.com/docker/docker/integration-cli/fixtures/plugin"
|
||||||
"github.com/docker/docker/integration-cli/registry"
|
"github.com/docker/docker/integration-cli/registry"
|
||||||
|
ienv "github.com/docker/docker/internal/test/environment"
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -57,20 +60,14 @@ func init() {
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
dockerBinary = testEnv.DockerBinary()
|
dockerBinary = testEnv.DockerBinary()
|
||||||
|
testEnv.Print()
|
||||||
if testEnv.LocalDaemon() {
|
os.Exit(m.Run())
|
||||||
fmt.Println("INFO: Testing against a local daemon")
|
|
||||||
} else {
|
|
||||||
fmt.Println("INFO: Testing against a remote daemon")
|
|
||||||
}
|
|
||||||
exitCode := m.Run()
|
|
||||||
os.Exit(exitCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
func Test(t *testing.T) {
|
||||||
cli.EnsureTestEnvIsLoaded(t)
|
cli.SetTestEnvironment(testEnv)
|
||||||
fakestorage.EnsureTestEnvIsLoaded(t)
|
fakestorage.SetTestEnvironment(&testEnv.Execution)
|
||||||
environment.ProtectImages(t, testEnv)
|
ienv.ProtectImages(t, &testEnv.Execution)
|
||||||
check.TestingT(t)
|
check.TestingT(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,13 +79,25 @@ type DockerSuite struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) OnTimeout(c *check.C) {
|
func (s *DockerSuite) OnTimeout(c *check.C) {
|
||||||
if testEnv.DaemonPID() > 0 && testEnv.LocalDaemon() {
|
path := filepath.Join(os.Getenv("DEST"), "docker.pid")
|
||||||
daemon.SignalDaemonDump(testEnv.DaemonPID())
|
b, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Failed to get daemon PID from %s\n", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawPid, err := strconv.ParseInt(string(b), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Failed to parse pid from %s: %s\n", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
daemonPid := int(rawPid)
|
||||||
|
if daemonPid > 0 && testEnv.IsLocalDaemon() {
|
||||||
|
daemon.SignalDaemonDump(daemonPid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerSuite) TearDownTest(c *check.C) {
|
func (s *DockerSuite) TearDownTest(c *check.C) {
|
||||||
testEnv.Clean(c, dockerBinary)
|
testEnv.Clean(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -11,9 +11,11 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/cli/build/fakecontext"
|
"github.com/docker/docker/integration-cli/cli/build/fakecontext"
|
||||||
"github.com/docker/docker/integration-cli/cli/build/fakestorage"
|
"github.com/docker/docker/integration-cli/cli/build/fakestorage"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testingT interface {
|
type testingT interface {
|
||||||
|
require.TestingT
|
||||||
logT
|
logT
|
||||||
Fatal(args ...interface{})
|
Fatal(args ...interface{})
|
||||||
Fatalf(string, ...interface{})
|
Fatalf(string, ...interface{})
|
||||||
|
|
|
@ -30,7 +30,7 @@ func ensureHTTPServerImage(t testingT) {
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmp)
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
goos := testEnv.DaemonPlatform()
|
goos := testEnv.DaemonInfo.OSType
|
||||||
if goos == "" {
|
if goos == "" {
|
||||||
goos = "linux"
|
goos = "linux"
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,39 +8,20 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/cli"
|
"github.com/docker/docker/integration-cli/cli"
|
||||||
"github.com/docker/docker/integration-cli/cli/build"
|
"github.com/docker/docker/integration-cli/cli/build"
|
||||||
"github.com/docker/docker/integration-cli/cli/build/fakecontext"
|
"github.com/docker/docker/integration-cli/cli/build/fakecontext"
|
||||||
"github.com/docker/docker/integration-cli/environment"
|
|
||||||
"github.com/docker/docker/integration-cli/request"
|
"github.com/docker/docker/integration-cli/request"
|
||||||
|
"github.com/docker/docker/internal/test/environment"
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var testEnv *environment.Execution
|
||||||
testEnv *environment.Execution
|
|
||||||
onlyOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
// EnsureTestEnvIsLoaded make sure the test environment is loaded for this package
|
|
||||||
func EnsureTestEnvIsLoaded(t testingT) {
|
|
||||||
var doIt bool
|
|
||||||
var err error
|
|
||||||
onlyOnce.Do(func() {
|
|
||||||
doIt = true
|
|
||||||
})
|
|
||||||
|
|
||||||
if !doIt {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
testEnv, err = environment.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error loading testenv : %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testingT interface {
|
type testingT interface {
|
||||||
|
require.TestingT
|
||||||
logT
|
logT
|
||||||
Fatal(args ...interface{})
|
Fatal(args ...interface{})
|
||||||
Fatalf(string, ...interface{})
|
Fatalf(string, ...interface{})
|
||||||
|
@ -58,11 +39,20 @@ type Fake interface {
|
||||||
CtxDir() string
|
CtxDir() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTestEnvironment sets a static test environment
|
||||||
|
// TODO: decouple this package from environment
|
||||||
|
func SetTestEnvironment(env *environment.Execution) {
|
||||||
|
testEnv = env
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a static file server that will be use as build context.
|
// New returns a static file server that will be use as build context.
|
||||||
func New(t testingT, dir string, modifiers ...func(*fakecontext.Fake) error) Fake {
|
func New(t testingT, dir string, modifiers ...func(*fakecontext.Fake) error) Fake {
|
||||||
|
if testEnv == nil {
|
||||||
|
t.Fatal("fakstorage package requires SetTestEnvironment() to be called before use.")
|
||||||
|
}
|
||||||
ctx := fakecontext.New(t, dir, modifiers...)
|
ctx := fakecontext.New(t, dir, modifiers...)
|
||||||
if testEnv.LocalDaemon() {
|
if testEnv.IsLocalDaemon() {
|
||||||
return newLocalFakeStorage(t, ctx)
|
return newLocalFakeStorage(ctx)
|
||||||
}
|
}
|
||||||
return newRemoteFileServer(t, ctx)
|
return newRemoteFileServer(t, ctx)
|
||||||
}
|
}
|
||||||
|
@ -86,7 +76,7 @@ func (s *localFileStorage) Close() error {
|
||||||
return s.Fake.Close()
|
return s.Fake.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLocalFakeStorage(t testingT, ctx *fakecontext.Fake) *localFileStorage {
|
func newLocalFakeStorage(ctx *fakecontext.Fake) *localFileStorage {
|
||||||
handler := http.FileServer(http.Dir(ctx.Dir))
|
handler := http.FileServer(http.Dir(ctx.Dir))
|
||||||
server := httptest.NewServer(handler)
|
server := httptest.NewServer(handler)
|
||||||
return &localFileStorage{
|
return &localFileStorage{
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/daemon"
|
"github.com/docker/docker/integration-cli/daemon"
|
||||||
|
@ -13,26 +12,12 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var testEnv *environment.Execution
|
||||||
testEnv *environment.Execution
|
|
||||||
onlyOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
// EnsureTestEnvIsLoaded make sure the test environment is loaded for this package
|
// SetTestEnvironment sets a static test environment
|
||||||
func EnsureTestEnvIsLoaded(t testingT) {
|
// TODO: decouple this package from environment
|
||||||
var doIt bool
|
func SetTestEnvironment(env *environment.Execution) {
|
||||||
var err error
|
testEnv = env
|
||||||
onlyOnce.Do(func() {
|
|
||||||
doIt = true
|
|
||||||
})
|
|
||||||
|
|
||||||
if !doIt {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
testEnv, err = environment.New()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error loading testenv : %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdOperator defines functions that can modify a command
|
// CmdOperator defines functions that can modify a command
|
||||||
|
@ -130,7 +115,7 @@ func Docker(cmd icmd.Cmd, cmdOperators ...CmdOperator) *icmd.Result {
|
||||||
// validateArgs is a checker to ensure tests are not running commands which are
|
// validateArgs is a checker to ensure tests are not running commands which are
|
||||||
// not supported on platforms. Specifically on Windows this is 'busybox top'.
|
// not supported on platforms. Specifically on Windows this is 'busybox top'.
|
||||||
func validateArgs(args ...string) error {
|
func validateArgs(args ...string) error {
|
||||||
if testEnv.DaemonPlatform() != "windows" {
|
if testEnv.DaemonInfo.OSType != "windows" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
foundBusybox := -1
|
foundBusybox := -1
|
||||||
|
|
|
@ -2215,7 +2215,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
|
||||||
|
|
||||||
out, err = inspectMountSourceField("dark_helmet", prefix+slash+`foo`)
|
out, err = inspectMountSourceField("dark_helmet", prefix+slash+`foo`)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.VolumesConfigPath())) {
|
if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.PlatformDefaults.VolumesConfigPath)) {
|
||||||
c.Fatalf("Volume was not defined for %s/foo\n%q", prefix, out)
|
c.Fatalf("Volume was not defined for %s/foo\n%q", prefix, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2226,7 +2226,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
|
||||||
|
|
||||||
out, err = inspectMountSourceField("dark_helmet", prefix+slash+"bar")
|
out, err = inspectMountSourceField("dark_helmet", prefix+slash+"bar")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.VolumesConfigPath())) {
|
if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.PlatformDefaults.VolumesConfigPath)) {
|
||||||
c.Fatalf("Volume was not defined for %s/bar\n%q", prefix, out)
|
c.Fatalf("Volume was not defined for %s/bar\n%q", prefix, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,7 +234,7 @@ func readFile(src string, c *check.C) (content string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerStorageFile(containerID, basename string) string {
|
func containerStorageFile(containerID, basename string) string {
|
||||||
return filepath.Join(testEnv.ContainerStoragePath(), containerID, basename)
|
return filepath.Join(testEnv.PlatformDefaults.ContainerStoragePath, containerID, basename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// docker commands that use this function must be run with the '-d' switch.
|
// docker commands that use this function must be run with the '-d' switch.
|
||||||
|
@ -266,7 +266,7 @@ func readContainerFileWithExec(c *check.C, containerID, filename string) []byte
|
||||||
|
|
||||||
// daemonTime provides the current time on the daemon host
|
// daemonTime provides the current time on the daemon host
|
||||||
func daemonTime(c *check.C) time.Time {
|
func daemonTime(c *check.C) time.Time {
|
||||||
if testEnv.LocalDaemon() {
|
if testEnv.IsLocalDaemon() {
|
||||||
return time.Now()
|
return time.Now()
|
||||||
}
|
}
|
||||||
cli, err := client.NewEnvClient()
|
cli, err := client.NewEnvClient()
|
||||||
|
|
|
@ -1,198 +0,0 @@
|
||||||
package environment
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/gotestyourself/gotestyourself/icmd"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testingT interface {
|
|
||||||
logT
|
|
||||||
Fatalf(string, ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type logT interface {
|
|
||||||
Logf(string, ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean the environment, preserving protected objects (images, containers, ...)
|
|
||||||
// and removing everything else. It's meant to run after any tests so that they don't
|
|
||||||
// depend on each others.
|
|
||||||
func (e *Execution) Clean(t testingT, dockerBinary string) {
|
|
||||||
cli, err := client.NewEnvClient()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
defer cli.Close()
|
|
||||||
|
|
||||||
if (e.DaemonPlatform() != "windows") || (e.DaemonPlatform() == "windows" && e.Isolation() == "hyperv") {
|
|
||||||
unpauseAllContainers(t, dockerBinary)
|
|
||||||
}
|
|
||||||
deleteAllContainers(t, dockerBinary)
|
|
||||||
deleteAllImages(t, dockerBinary, e.protectedElements.images)
|
|
||||||
deleteAllVolumes(t, cli)
|
|
||||||
deleteAllNetworks(t, cli, e.DaemonPlatform())
|
|
||||||
if e.DaemonPlatform() == "linux" {
|
|
||||||
deleteAllPlugins(t, cli, dockerBinary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func unpauseAllContainers(t testingT, dockerBinary string) {
|
|
||||||
containers := getPausedContainers(t, dockerBinary)
|
|
||||||
if len(containers) > 0 {
|
|
||||||
icmd.RunCommand(dockerBinary, append([]string{"unpause"}, containers...)...).Assert(t, icmd.Success)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPausedContainers(t testingT, dockerBinary string) []string {
|
|
||||||
result := icmd.RunCommand(dockerBinary, "ps", "-f", "status=paused", "-q", "-a")
|
|
||||||
result.Assert(t, icmd.Success)
|
|
||||||
return strings.Fields(result.Combined())
|
|
||||||
}
|
|
||||||
|
|
||||||
var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
|
|
||||||
|
|
||||||
func deleteAllContainers(t testingT, dockerBinary string) {
|
|
||||||
containers := getAllContainers(t, dockerBinary)
|
|
||||||
if len(containers) > 0 {
|
|
||||||
result := icmd.RunCommand(dockerBinary, append([]string{"rm", "-fv"}, containers...)...)
|
|
||||||
if result.Error != nil {
|
|
||||||
// If the error is "No such container: ..." this means the container doesn't exists anymore,
|
|
||||||
// or if it is "... removal of container ... is already in progress" it will be removed eventually.
|
|
||||||
// We can safely ignore those.
|
|
||||||
if strings.Contains(result.Stderr(), "No such container") || alreadyExists.MatchString(result.Stderr()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Fatalf("error removing containers %v : %v (%s)", containers, result.Error, result.Combined())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAllContainers(t testingT, dockerBinary string) []string {
|
|
||||||
result := icmd.RunCommand(dockerBinary, "ps", "-q", "-a")
|
|
||||||
result.Assert(t, icmd.Success)
|
|
||||||
return strings.Fields(result.Combined())
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteAllImages(t testingT, dockerBinary string, protectedImages map[string]struct{}) {
|
|
||||||
result := icmd.RunCommand(dockerBinary, "images", "--digests")
|
|
||||||
result.Assert(t, icmd.Success)
|
|
||||||
lines := strings.Split(string(result.Combined()), "\n")[1:]
|
|
||||||
imgMap := map[string]struct{}{}
|
|
||||||
for _, l := range lines {
|
|
||||||
if l == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Fields(l)
|
|
||||||
imgTag := fields[0] + ":" + fields[1]
|
|
||||||
if _, ok := protectedImages[imgTag]; !ok {
|
|
||||||
if fields[0] == "<none>" || fields[1] == "<none>" {
|
|
||||||
if fields[2] != "<none>" {
|
|
||||||
imgMap[fields[0]+"@"+fields[2]] = struct{}{}
|
|
||||||
} else {
|
|
||||||
imgMap[fields[3]] = struct{}{}
|
|
||||||
}
|
|
||||||
// continue
|
|
||||||
} else {
|
|
||||||
imgMap[imgTag] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(imgMap) != 0 {
|
|
||||||
imgs := make([]string, 0, len(imgMap))
|
|
||||||
for k := range imgMap {
|
|
||||||
imgs = append(imgs, k)
|
|
||||||
}
|
|
||||||
icmd.RunCommand(dockerBinary, append([]string{"rmi", "-f"}, imgs...)...).Assert(t, icmd.Success)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteAllVolumes(t testingT, c client.APIClient) {
|
|
||||||
var errs []string
|
|
||||||
volumes, err := getAllVolumes(c)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
for _, v := range volumes {
|
|
||||||
err := c.VolumeRemove(context.Background(), v.Name, true)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatalf("%v", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAllVolumes(c client.APIClient) ([]*types.Volume, error) {
|
|
||||||
volumes, err := c.VolumeList(context.Background(), filters.Args{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return volumes.Volumes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteAllNetworks(t testingT, c client.APIClient, daemonPlatform string) {
|
|
||||||
networks, err := getAllNetworks(c)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
var errs []string
|
|
||||||
for _, n := range networks {
|
|
||||||
if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
|
|
||||||
// nat is a pre-defined network on Windows and cannot be removed
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := c.NetworkRemove(context.Background(), n.ID)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatalf("%v", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAllNetworks(c client.APIClient) ([]types.NetworkResource, error) {
|
|
||||||
networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return networks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteAllPlugins(t testingT, c client.APIClient, dockerBinary string) {
|
|
||||||
plugins, err := getAllPlugins(c)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
var errs []string
|
|
||||||
for _, p := range plugins {
|
|
||||||
err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true})
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
t.Fatalf("%v", strings.Join(errs, "\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAllPlugins(c client.APIClient) (types.PluginsListResponse, error) {
|
|
||||||
plugins, err := c.PluginList(context.Background(), filters.Args{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return plugins, nil
|
|
||||||
}
|
|
|
@ -1,19 +1,11 @@
|
||||||
package environment
|
package environment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"os/exec"
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/internal/test/environment"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -23,89 +15,28 @@ var (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if DefaultClientBinary == "" {
|
if DefaultClientBinary == "" {
|
||||||
// TODO: to be removed once we no longer depend on the docker cli for integration tests
|
|
||||||
//panic("TEST_CLIENT_BINARY must be set")
|
|
||||||
DefaultClientBinary = "docker"
|
DefaultClientBinary = "docker"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execution holds informations about the test execution environment.
|
// Execution contains information about the current test execution and daemon
|
||||||
|
// under test
|
||||||
type Execution struct {
|
type Execution struct {
|
||||||
daemonPlatform string
|
environment.Execution
|
||||||
localDaemon bool
|
|
||||||
experimentalDaemon bool
|
|
||||||
daemonStorageDriver string
|
|
||||||
isolation container.Isolation
|
|
||||||
daemonPid int
|
|
||||||
daemonKernelVersion string
|
|
||||||
// For a local daemon on Linux, these values will be used for testing
|
|
||||||
// user namespace support as the standard graph path(s) will be
|
|
||||||
// appended with the root remapped uid.gid prefix
|
|
||||||
dockerBasePath string
|
|
||||||
volumesConfigPath string
|
|
||||||
containerStoragePath string
|
|
||||||
// baseImage is the name of the base image for testing
|
|
||||||
// Environment variable WINDOWS_BASE_IMAGE can override this
|
|
||||||
baseImage string
|
|
||||||
dockerBinary string
|
dockerBinary string
|
||||||
|
|
||||||
protectedElements protectedElements
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Execution struct
|
// DockerBinary returns the docker binary for this testing environment
|
||||||
|
func (e *Execution) DockerBinary() string {
|
||||||
|
return e.dockerBinary
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns details about the testing environment
|
||||||
func New() (*Execution, error) {
|
func New() (*Execution, error) {
|
||||||
localDaemon := true
|
env, err := environment.New()
|
||||||
// Deterministically working out the environment in which CI is running
|
|
||||||
// to evaluate whether the daemon is local or remote is not possible through
|
|
||||||
// a build tag.
|
|
||||||
//
|
|
||||||
// For example Windows to Linux CI under Jenkins tests the 64-bit
|
|
||||||
// Windows binary build with the daemon build tag, but calls a remote
|
|
||||||
// Linux daemon.
|
|
||||||
//
|
|
||||||
// We can't just say if Windows then assume the daemon is local as at
|
|
||||||
// some point, we will be testing the Windows CLI against a Windows daemon.
|
|
||||||
//
|
|
||||||
// Similarly, it will be perfectly valid to also run CLI tests from
|
|
||||||
// a Linux CLI (built with the daemon tag) against a Windows daemon.
|
|
||||||
if len(os.Getenv("DOCKER_REMOTE_DAEMON")) > 0 {
|
|
||||||
localDaemon = false
|
|
||||||
}
|
|
||||||
info, err := getDaemonDockerInfo()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
daemonPlatform := info.OSType
|
|
||||||
if daemonPlatform != "linux" && daemonPlatform != "windows" {
|
|
||||||
return nil, fmt.Errorf("Cannot run tests against platform: %s", daemonPlatform)
|
|
||||||
}
|
|
||||||
baseImage := "scratch"
|
|
||||||
volumesConfigPath := filepath.Join(info.DockerRootDir, "volumes")
|
|
||||||
containerStoragePath := filepath.Join(info.DockerRootDir, "containers")
|
|
||||||
// Make sure in context of daemon, not the local platform. Note we can't
|
|
||||||
// use filepath.FromSlash or ToSlash here as they are a no-op on Unix.
|
|
||||||
if daemonPlatform == "windows" {
|
|
||||||
volumesConfigPath = strings.Replace(volumesConfigPath, `/`, `\`, -1)
|
|
||||||
containerStoragePath = strings.Replace(containerStoragePath, `/`, `\`, -1)
|
|
||||||
|
|
||||||
baseImage = "microsoft/windowsservercore"
|
|
||||||
if len(os.Getenv("WINDOWS_BASE_IMAGE")) > 0 {
|
|
||||||
baseImage = os.Getenv("WINDOWS_BASE_IMAGE")
|
|
||||||
fmt.Println("INFO: Windows Base image is ", baseImage)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
volumesConfigPath = strings.Replace(volumesConfigPath, `\`, `/`, -1)
|
|
||||||
containerStoragePath = strings.Replace(containerStoragePath, `\`, `/`, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var daemonPid int
|
|
||||||
dest := os.Getenv("DEST")
|
|
||||||
b, err := ioutil.ReadFile(filepath.Join(dest, "docker.pid"))
|
|
||||||
if err == nil {
|
|
||||||
if p, err := strconv.ParseInt(string(b), 10, 32); err == nil {
|
|
||||||
daemonPid = int(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerBinary, err := exec.LookPath(DefaultClientBinary)
|
dockerBinary, err := exec.LookPath(DefaultClientBinary)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -113,117 +44,36 @@ func New() (*Execution, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Execution{
|
return &Execution{
|
||||||
localDaemon: localDaemon,
|
Execution: *env,
|
||||||
daemonPlatform: daemonPlatform,
|
|
||||||
daemonStorageDriver: info.Driver,
|
|
||||||
daemonKernelVersion: info.KernelVersion,
|
|
||||||
dockerBasePath: info.DockerRootDir,
|
|
||||||
volumesConfigPath: volumesConfigPath,
|
|
||||||
containerStoragePath: containerStoragePath,
|
|
||||||
isolation: info.Isolation,
|
|
||||||
daemonPid: daemonPid,
|
|
||||||
experimentalDaemon: info.ExperimentalBuild,
|
|
||||||
baseImage: baseImage,
|
|
||||||
dockerBinary: dockerBinary,
|
dockerBinary: dockerBinary,
|
||||||
protectedElements: protectedElements{
|
|
||||||
images: map[string]struct{}{},
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
func getDaemonDockerInfo() (types.Info, error) {
|
|
||||||
// FIXME(vdemeester) should be safe to use as is
|
// DockerBasePath is the base path of the docker folder (by default it is -/var/run/docker)
|
||||||
client, err := client.NewEnvClient()
|
// TODO: remove
|
||||||
if err != nil {
|
// Deprecated: use Execution.DaemonInfo.DockerRootDir
|
||||||
return types.Info{}, err
|
func (e *Execution) DockerBasePath() string {
|
||||||
}
|
return e.DaemonInfo.DockerRootDir
|
||||||
return client.Info(context.Background())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalDaemon is true if the daemon under test is on the same
|
// ExperimentalDaemon tell whether the main daemon has
|
||||||
// host as the CLI.
|
// experimental features enabled or not
|
||||||
func (e *Execution) LocalDaemon() bool {
|
// Deprecated: use DaemonInfo.ExperimentalBuild
|
||||||
return e.localDaemon
|
func (e *Execution) ExperimentalDaemon() bool {
|
||||||
|
return e.DaemonInfo.ExperimentalBuild
|
||||||
}
|
}
|
||||||
|
|
||||||
// DaemonPlatform is held globally so that tests can make intelligent
|
// DaemonPlatform is held globally so that tests can make intelligent
|
||||||
// decisions on how to configure themselves according to the platform
|
// decisions on how to configure themselves according to the platform
|
||||||
// of the daemon. This is initialized in docker_utils by sending
|
// of the daemon. This is initialized in docker_utils by sending
|
||||||
// a version call to the daemon and examining the response header.
|
// a version call to the daemon and examining the response header.
|
||||||
|
// Deprecated: use Execution.DaemonInfo.OSType
|
||||||
func (e *Execution) DaemonPlatform() string {
|
func (e *Execution) DaemonPlatform() string {
|
||||||
return e.daemonPlatform
|
return e.DaemonInfo.OSType
|
||||||
}
|
|
||||||
|
|
||||||
// DockerBasePath is the base path of the docker folder (by default it is -/var/run/docker)
|
|
||||||
func (e *Execution) DockerBasePath() string {
|
|
||||||
return e.dockerBasePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumesConfigPath is the path of the volume configuration for the testing daemon
|
|
||||||
func (e *Execution) VolumesConfigPath() string {
|
|
||||||
return e.volumesConfigPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerStoragePath is the path where the container are stored for the testing daemon
|
|
||||||
func (e *Execution) ContainerStoragePath() string {
|
|
||||||
return e.containerStoragePath
|
|
||||||
}
|
|
||||||
|
|
||||||
// DaemonStorageDriver is held globally so that tests can know the storage
|
|
||||||
// driver of the daemon. This is initialized in docker_utils by sending
|
|
||||||
// a version call to the daemon and examining the response header.
|
|
||||||
func (e *Execution) DaemonStorageDriver() string {
|
|
||||||
return e.daemonStorageDriver
|
|
||||||
}
|
|
||||||
|
|
||||||
// Isolation is the isolation mode of the daemon under test
|
|
||||||
func (e *Execution) Isolation() container.Isolation {
|
|
||||||
return e.isolation
|
|
||||||
}
|
|
||||||
|
|
||||||
// DaemonPID is the pid of the main test daemon
|
|
||||||
func (e *Execution) DaemonPID() int {
|
|
||||||
return e.daemonPid
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExperimentalDaemon tell whether the main daemon has
|
|
||||||
// experimental features enabled or not
|
|
||||||
func (e *Execution) ExperimentalDaemon() bool {
|
|
||||||
return e.experimentalDaemon
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MinimalBaseImage is the image used for minimal builds (it depends on the platform)
|
// MinimalBaseImage is the image used for minimal builds (it depends on the platform)
|
||||||
|
// Deprecated: use Execution.PlatformDefaults.BaseImage
|
||||||
func (e *Execution) MinimalBaseImage() string {
|
func (e *Execution) MinimalBaseImage() string {
|
||||||
return e.baseImage
|
return e.PlatformDefaults.BaseImage
|
||||||
}
|
|
||||||
|
|
||||||
// DaemonKernelVersion is the kernel version of the daemon as a string, as returned
|
|
||||||
// by an INFO call to the daemon.
|
|
||||||
func (e *Execution) DaemonKernelVersion() string {
|
|
||||||
return e.daemonKernelVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
// DaemonKernelVersionNumeric is the kernel version of the daemon as an integer.
|
|
||||||
// Mostly useful on Windows where DaemonKernelVersion holds the full string such
|
|
||||||
// as `10.0 14393 (14393.447.amd64fre.rs1_release_inmarket.161102-0100)`, but
|
|
||||||
// integration tests really only need the `14393` piece to make decisions.
|
|
||||||
func (e *Execution) DaemonKernelVersionNumeric() int {
|
|
||||||
if e.daemonPlatform != "windows" {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
v, _ := strconv.Atoi(strings.Split(e.daemonKernelVersion, " ")[1])
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// DockerBinary returns the docker binary for this testing environment
|
|
||||||
func (e *Execution) DockerBinary() string {
|
|
||||||
return e.dockerBinary
|
|
||||||
}
|
|
||||||
|
|
||||||
// DaemonHost return the daemon host string for this test execution
|
|
||||||
func DaemonHost() string {
|
|
||||||
daemonURLStr := "unix://" + opts.DefaultUnixSocket
|
|
||||||
if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
|
|
||||||
daemonURLStr = daemonHostVar
|
|
||||||
}
|
|
||||||
return daemonURLStr
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
package environment
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/fixtures/load"
|
|
||||||
"github.com/gotestyourself/gotestyourself/icmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
type protectedElements struct {
|
|
||||||
images map[string]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProtectImage adds the specified image(s) to be protected in case of clean
|
|
||||||
func (e *Execution) ProtectImage(t testingT, images ...string) {
|
|
||||||
for _, image := range images {
|
|
||||||
e.protectedElements.images[image] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProtectImages protects existing images and on linux frozen images from being
|
|
||||||
// cleaned up at the end of test runs
|
|
||||||
func ProtectImages(t testingT, testEnv *Execution) {
|
|
||||||
images := getExistingImages(t, testEnv)
|
|
||||||
|
|
||||||
if testEnv.DaemonPlatform() == "linux" {
|
|
||||||
images = append(images, ensureFrozenImagesLinux(t, testEnv)...)
|
|
||||||
}
|
|
||||||
testEnv.ProtectImage(t, images...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExistingImages(t testingT, testEnv *Execution) []string {
|
|
||||||
// TODO: use API instead of cli
|
|
||||||
result := icmd.RunCommand(testEnv.dockerBinary, "images", "-f", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}")
|
|
||||||
result.Assert(t, icmd.Success)
|
|
||||||
return strings.Split(strings.TrimSpace(result.Stdout()), "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureFrozenImagesLinux(t testingT, testEnv *Execution) []string {
|
|
||||||
images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
|
|
||||||
err := load.FrozenImagesLinux(testEnv.DockerBinary(), images...)
|
|
||||||
if err != nil {
|
|
||||||
result := icmd.RunCommand(testEnv.DockerBinary(), "image", "ls")
|
|
||||||
t.Logf(result.String())
|
|
||||||
t.Fatalf("%+v", err)
|
|
||||||
}
|
|
||||||
return images
|
|
||||||
}
|
|
|
@ -79,7 +79,7 @@ func ensureSyscallTest(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureSyscallTestBuild(c *check.C) {
|
func ensureSyscallTestBuild(c *check.C) {
|
||||||
err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie")
|
err := load.FrozenImagesLinux(testEnv.APIClient(), "buildpack-deps:jessie")
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
var buildArgs []string
|
var buildArgs []string
|
||||||
|
@ -126,7 +126,7 @@ func ensureNNPTest(c *check.C) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureNNPTestBuild(c *check.C) {
|
func ensureNNPTestBuild(c *check.C) {
|
||||||
err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie")
|
err := load.FrozenImagesLinux(testEnv.APIClient(), "buildpack-deps:jessie")
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
var buildArgs []string
|
var buildArgs []string
|
||||||
|
|
|
@ -8,7 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type skipT interface {
|
// SkipT is the interface required to skip tests
|
||||||
|
type SkipT interface {
|
||||||
Skip(reason string)
|
Skip(reason string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ type Test func() bool
|
||||||
|
|
||||||
// Is checks if the environment satisfies the requirements
|
// Is checks if the environment satisfies the requirements
|
||||||
// for the test to run or skips the tests.
|
// for the test to run or skips the tests.
|
||||||
func Is(s skipT, requirements ...Test) {
|
func Is(s SkipT, requirements ...Test) {
|
||||||
for _, r := range requirements {
|
for _, r := range requirements {
|
||||||
isValid := r()
|
isValid := r()
|
||||||
if !isValid {
|
if !isValid {
|
||||||
|
|
|
@ -6,57 +6,47 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/requirement"
|
"github.com/docker/docker/integration-cli/requirement"
|
||||||
"github.com/go-check/check"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func PlatformIs(platform string) bool {
|
|
||||||
return testEnv.DaemonPlatform() == platform
|
|
||||||
}
|
|
||||||
|
|
||||||
func ArchitectureIs(arch string) bool {
|
|
||||||
return os.Getenv("DOCKER_ENGINE_GOARCH") == arch
|
|
||||||
}
|
|
||||||
|
|
||||||
func ArchitectureIsNot(arch string) bool {
|
func ArchitectureIsNot(arch string) bool {
|
||||||
return os.Getenv("DOCKER_ENGINE_GOARCH") != arch
|
return os.Getenv("DOCKER_ENGINE_GOARCH") != arch
|
||||||
}
|
}
|
||||||
|
|
||||||
func StorageDriverIs(storageDriver string) bool {
|
|
||||||
return strings.HasPrefix(testEnv.DaemonStorageDriver(), storageDriver)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StorageDriverIsNot(storageDriver string) bool {
|
|
||||||
return !strings.HasPrefix(testEnv.DaemonStorageDriver(), storageDriver)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DaemonIsWindows() bool {
|
func DaemonIsWindows() bool {
|
||||||
return PlatformIs("windows")
|
return testEnv.DaemonInfo.OSType == "windows"
|
||||||
}
|
}
|
||||||
|
|
||||||
func DaemonIsWindowsAtLeastBuild(buildNumber int) func() bool {
|
func DaemonIsWindowsAtLeastBuild(buildNumber int) func() bool {
|
||||||
return func() bool {
|
return func() bool {
|
||||||
return DaemonIsWindows() && testEnv.DaemonKernelVersionNumeric() >= buildNumber
|
if testEnv.DaemonInfo.OSType != "windows" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
version := testEnv.DaemonInfo.KernelVersion
|
||||||
|
numVersion, _ := strconv.Atoi(strings.Split(version, " ")[1])
|
||||||
|
return numVersion >= buildNumber
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DaemonIsLinux() bool {
|
func DaemonIsLinux() bool {
|
||||||
return PlatformIs("linux")
|
return testEnv.DaemonInfo.OSType == "linux"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: use skip.IfCondition(t, !testEnv.DaemonInfo.ExperimentalBuild)
|
||||||
func ExperimentalDaemon() bool {
|
func ExperimentalDaemon() bool {
|
||||||
return testEnv.ExperimentalDaemon()
|
return testEnv.DaemonInfo.ExperimentalBuild
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotExperimentalDaemon() bool {
|
func NotExperimentalDaemon() bool {
|
||||||
return !testEnv.ExperimentalDaemon()
|
return !testEnv.DaemonInfo.ExperimentalBuild
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsAmd64() bool {
|
func IsAmd64() bool {
|
||||||
return ArchitectureIs("amd64")
|
return os.Getenv("DOCKER_ENGINE_GOARCH") == "amd64"
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotArm() bool {
|
func NotArm() bool {
|
||||||
|
@ -76,7 +66,7 @@ func NotS390X() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SameHostDaemon() bool {
|
func SameHostDaemon() bool {
|
||||||
return testEnv.LocalDaemon()
|
return testEnv.IsLocalDaemon()
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnixCli() bool {
|
func UnixCli() bool {
|
||||||
|
@ -127,12 +117,8 @@ func NotaryServerHosting() bool {
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotOverlay() bool {
|
|
||||||
return StorageDriverIsNot("overlay")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Devicemapper() bool {
|
func Devicemapper() bool {
|
||||||
return StorageDriverIs("devicemapper")
|
return strings.HasPrefix(testEnv.DaemonInfo.Driver, "devicemapper")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IPv6() bool {
|
func IPv6() bool {
|
||||||
|
@ -177,21 +163,21 @@ func UserNamespaceInKernel() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsPausable() bool {
|
func IsPausable() bool {
|
||||||
if testEnv.DaemonPlatform() == "windows" {
|
if testEnv.DaemonInfo.OSType == "windows" {
|
||||||
return testEnv.Isolation() == "hyperv"
|
return testEnv.DaemonInfo.Isolation == "hyperv"
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func NotPausable() bool {
|
func NotPausable() bool {
|
||||||
if testEnv.DaemonPlatform() == "windows" {
|
if testEnv.DaemonInfo.OSType == "windows" {
|
||||||
return testEnv.Isolation() == "process"
|
return testEnv.DaemonInfo.Isolation == "process"
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsolationIs(expectedIsolation string) bool {
|
func IsolationIs(expectedIsolation string) bool {
|
||||||
return testEnv.DaemonPlatform() == "windows" && string(testEnv.Isolation()) == expectedIsolation
|
return testEnv.DaemonInfo.OSType == "windows" && string(testEnv.DaemonInfo.Isolation) == expectedIsolation
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsolationIsHyperv() bool {
|
func IsolationIsHyperv() bool {
|
||||||
|
@ -204,6 +190,6 @@ func IsolationIsProcess() bool {
|
||||||
|
|
||||||
// testRequires checks if the environment satisfies the requirements
|
// testRequires checks if the environment satisfies the requirements
|
||||||
// for the test to run or skips the tests.
|
// for the test to run or skips the tests.
|
||||||
func testRequires(c *check.C, requirements ...requirement.Test) {
|
func testRequires(c requirement.SkipT, requirements ...requirement.Test) {
|
||||||
requirement.Is(c, requirements...)
|
requirement.Is(c, requirements...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ func overlay2Supported() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
daemonV, err := kernel.ParseRelease(testEnv.DaemonKernelVersion())
|
daemonV, err := kernel.ParseRelease(testEnv.DaemonInfo.KernelVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,10 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/environment"
|
"github.com/docker/docker/internal/test/environment"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var testEnv *environment.Execution
|
||||||
testEnv *environment.Execution
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
var err error
|
var err error
|
||||||
|
@ -20,18 +18,11 @@ func TestMain(m *testing.M) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace this with `testEnv.Print()` to print the full env
|
testEnv.Print()
|
||||||
if testEnv.LocalDaemon() {
|
os.Exit(m.Run())
|
||||||
fmt.Println("INFO: Testing against a local daemon")
|
|
||||||
} else {
|
|
||||||
fmt.Println("INFO: Testing against a remote daemon")
|
|
||||||
}
|
|
||||||
|
|
||||||
res := m.Run()
|
|
||||||
os.Exit(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupTest(t *testing.T) func() {
|
func setupTest(t *testing.T) func() {
|
||||||
environment.ProtectImages(t, testEnv)
|
environment.ProtectImages(t, testEnv)
|
||||||
return func() { testEnv.Clean(t, testEnv.DockerBinary()) }
|
return func() { testEnv.Clean(t) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ const defaultSwarmPort = 2477
|
||||||
func newSwarm(t *testing.T) *daemon.Swarm {
|
func newSwarm(t *testing.T) *daemon.Swarm {
|
||||||
d := &daemon.Swarm{
|
d := &daemon.Swarm{
|
||||||
Daemon: daemon.New(t, "", dockerdBinary, daemon.Config{
|
Daemon: daemon.New(t, "", dockerdBinary, daemon.Config{
|
||||||
Experimental: testEnv.ExperimentalDaemon(),
|
Experimental: testEnv.DaemonInfo.ExperimentalBuild,
|
||||||
}),
|
}),
|
||||||
// TODO: better method of finding an unused port
|
// TODO: better method of finding an unused port
|
||||||
Port: defaultSwarmPort,
|
Port: defaultSwarmPort,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/environment"
|
"github.com/docker/docker/internal/test/environment"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testEnv *environment.Execution
|
var testEnv *environment.Execution
|
||||||
|
@ -20,18 +20,11 @@ func TestMain(m *testing.M) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace this with `testEnv.Print()` to print the full env
|
testEnv.Print()
|
||||||
if testEnv.LocalDaemon() {
|
os.Exit(m.Run())
|
||||||
fmt.Println("INFO: Testing against a local daemon")
|
|
||||||
} else {
|
|
||||||
fmt.Println("INFO: Testing against a remote daemon")
|
|
||||||
}
|
|
||||||
|
|
||||||
res := m.Run()
|
|
||||||
os.Exit(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupTest(t *testing.T) func() {
|
func setupTest(t *testing.T) func() {
|
||||||
environment.ProtectImages(t, testEnv)
|
environment.ProtectImages(t, testEnv)
|
||||||
return func() { testEnv.Clean(t, testEnv.DockerBinary()) }
|
return func() { testEnv.Clean(t) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
package environment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testingT interface {
|
||||||
|
require.TestingT
|
||||||
|
logT
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type logT interface {
|
||||||
|
Logf(string, ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the environment, preserving protected objects (images, containers, ...)
|
||||||
|
// and removing everything else. It's meant to run after any tests so that they don't
|
||||||
|
// depend on each others.
|
||||||
|
func (e *Execution) Clean(t testingT) {
|
||||||
|
client := e.APIClient()
|
||||||
|
|
||||||
|
platform := e.DaemonInfo.OSType
|
||||||
|
if (platform != "windows") || (platform == "windows" && e.DaemonInfo.Isolation == "hyperv") {
|
||||||
|
unpauseAllContainers(t, client)
|
||||||
|
}
|
||||||
|
deleteAllContainers(t, client)
|
||||||
|
deleteAllImages(t, client, e.protectedElements.images)
|
||||||
|
deleteAllVolumes(t, client)
|
||||||
|
deleteAllNetworks(t, client, platform)
|
||||||
|
if platform == "linux" {
|
||||||
|
deleteAllPlugins(t, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpauseAllContainers(t testingT, client client.ContainerAPIClient) {
|
||||||
|
ctx := context.Background()
|
||||||
|
containers := getPausedContainers(ctx, t, client)
|
||||||
|
if len(containers) > 0 {
|
||||||
|
for _, container := range containers {
|
||||||
|
err := client.ContainerUnpause(ctx, container.ID)
|
||||||
|
assert.NoError(t, err, "failed to unpause container %s", container.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPausedContainers(ctx context.Context, t testingT, client client.ContainerAPIClient) []types.Container {
|
||||||
|
filter := filters.NewArgs()
|
||||||
|
filter.Add("status", "paused")
|
||||||
|
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
|
||||||
|
Filters: filter,
|
||||||
|
Quiet: true,
|
||||||
|
All: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, "failed to list containers")
|
||||||
|
return containers
|
||||||
|
}
|
||||||
|
|
||||||
|
var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
|
||||||
|
|
||||||
|
func deleteAllContainers(t testingT, apiclient client.ContainerAPIClient) {
|
||||||
|
ctx := context.Background()
|
||||||
|
containers := getAllContainers(ctx, t, apiclient)
|
||||||
|
if len(containers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
err := apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{
|
||||||
|
Force: true,
|
||||||
|
RemoveVolumes: true,
|
||||||
|
})
|
||||||
|
if err == nil || client.IsErrNotFound(err) || alreadyExists.MatchString(err.Error()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.NoError(t, err, "failed to remove %s", container.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllContainers(ctx context.Context, t testingT, client client.ContainerAPIClient) []types.Container {
|
||||||
|
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
|
||||||
|
Quiet: true,
|
||||||
|
All: true,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, "failed to list containers")
|
||||||
|
return containers
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAllImages(t testingT, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
|
||||||
|
images, err := apiclient.ImageList(context.Background(), types.ImageListOptions{})
|
||||||
|
assert.NoError(t, err, "failed to list images")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
for _, image := range images {
|
||||||
|
tags := tagsFromImageSummary(image)
|
||||||
|
if len(tags) == 0 {
|
||||||
|
t.Logf("Removing image %s", image.ID)
|
||||||
|
removeImage(ctx, t, apiclient, image.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, tag := range tags {
|
||||||
|
if _, ok := protectedImages[tag]; !ok {
|
||||||
|
t.Logf("Removing image %s", tag)
|
||||||
|
removeImage(ctx, t, apiclient, tag)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeImage(ctx context.Context, t testingT, apiclient client.ImageAPIClient, ref string) {
|
||||||
|
_, err := apiclient.ImageRemove(ctx, ref, types.ImageRemoveOptions{
|
||||||
|
Force: true,
|
||||||
|
})
|
||||||
|
if client.IsErrNotFound(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(t, err, "failed to remove image %s", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAllVolumes(t testingT, c client.VolumeAPIClient) {
|
||||||
|
volumes, err := c.VolumeList(context.Background(), filters.Args{})
|
||||||
|
assert.NoError(t, err, "failed to list volumes")
|
||||||
|
|
||||||
|
for _, v := range volumes.Volumes {
|
||||||
|
err := c.VolumeRemove(context.Background(), v.Name, true)
|
||||||
|
assert.NoError(t, err, "failed to remove volume %s", v.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAllNetworks(t testingT, c client.NetworkAPIClient, daemonPlatform string) {
|
||||||
|
networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
|
||||||
|
assert.NoError(t, err, "failed to list networks")
|
||||||
|
|
||||||
|
for _, n := range networks {
|
||||||
|
if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
|
||||||
|
// nat is a pre-defined network on Windows and cannot be removed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := c.NetworkRemove(context.Background(), n.ID)
|
||||||
|
assert.NoError(t, err, "failed to remove network %s", n.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAllPlugins(t testingT, c client.PluginAPIClient) {
|
||||||
|
plugins, err := c.PluginList(context.Background(), filters.Args{})
|
||||||
|
assert.NoError(t, err, "failed to list plugins")
|
||||||
|
|
||||||
|
for _, p := range plugins {
|
||||||
|
err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true})
|
||||||
|
assert.NoError(t, err, "failed to remove plugin %s", p.ID)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package environment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Execution contains information about the current test execution and daemon
|
||||||
|
// under test
|
||||||
|
type Execution struct {
|
||||||
|
client client.APIClient
|
||||||
|
DaemonInfo types.Info
|
||||||
|
PlatformDefaults PlatformDefaults
|
||||||
|
protectedElements protectedElements
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlatformDefaults are defaults values for the platform of the daemon under test
|
||||||
|
type PlatformDefaults struct {
|
||||||
|
BaseImage string
|
||||||
|
VolumesConfigPath string
|
||||||
|
ContainerStoragePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Execution struct
|
||||||
|
func New() (*Execution, error) {
|
||||||
|
client, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to create client")
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := client.Info(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to get info from daemon")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Execution{
|
||||||
|
client: client,
|
||||||
|
DaemonInfo: info,
|
||||||
|
PlatformDefaults: getPlatformDefaults(info),
|
||||||
|
protectedElements: newProtectedElements(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPlatformDefaults(info types.Info) PlatformDefaults {
|
||||||
|
volumesPath := filepath.Join(info.DockerRootDir, "volumes")
|
||||||
|
containersPath := filepath.Join(info.DockerRootDir, "containers")
|
||||||
|
|
||||||
|
switch info.OSType {
|
||||||
|
case "linux":
|
||||||
|
return PlatformDefaults{
|
||||||
|
BaseImage: "scratch",
|
||||||
|
VolumesConfigPath: toSlash(volumesPath),
|
||||||
|
ContainerStoragePath: toSlash(containersPath),
|
||||||
|
}
|
||||||
|
case "windows":
|
||||||
|
baseImage := "microsoft/windowsservercore"
|
||||||
|
if override := os.Getenv("WINDOWS_BASE_IMAGE"); override != "" {
|
||||||
|
baseImage = override
|
||||||
|
fmt.Println("INFO: Windows Base image is ", baseImage)
|
||||||
|
}
|
||||||
|
return PlatformDefaults{
|
||||||
|
BaseImage: baseImage,
|
||||||
|
VolumesConfigPath: filepath.FromSlash(volumesPath),
|
||||||
|
ContainerStoragePath: filepath.FromSlash(containersPath),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown info.OSType for daemon: %s", info.OSType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure in context of daemon, not the local platform. Note we can't
|
||||||
|
// use filepath.FromSlash or ToSlash here as they are a no-op on Unix.
|
||||||
|
func toSlash(path string) string {
|
||||||
|
return strings.Replace(path, `\`, `/`, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLocalDaemon is true if the daemon under test is on the same
|
||||||
|
// host as the CLI.
|
||||||
|
//
|
||||||
|
// Deterministically working out the environment in which CI is running
|
||||||
|
// to evaluate whether the daemon is local or remote is not possible through
|
||||||
|
// a build tag.
|
||||||
|
//
|
||||||
|
// For example Windows to Linux CI under Jenkins tests the 64-bit
|
||||||
|
// Windows binary build with the daemon build tag, but calls a remote
|
||||||
|
// Linux daemon.
|
||||||
|
//
|
||||||
|
// We can't just say if Windows then assume the daemon is local as at
|
||||||
|
// some point, we will be testing the Windows CLI against a Windows daemon.
|
||||||
|
//
|
||||||
|
// Similarly, it will be perfectly valid to also run CLI tests from
|
||||||
|
// a Linux CLI (built with the daemon tag) against a Windows daemon.
|
||||||
|
func (e *Execution) IsLocalDaemon() bool {
|
||||||
|
return os.Getenv("DOCKER_REMOTE_DAEMON") == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the execution details to stdout
|
||||||
|
// TODO: print everything
|
||||||
|
func (e *Execution) Print() {
|
||||||
|
if e.IsLocalDaemon() {
|
||||||
|
fmt.Println("INFO: Testing against a local daemon")
|
||||||
|
} else {
|
||||||
|
fmt.Println("INFO: Testing against a remote daemon")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIClient returns an APIClient connected to the daemon under test
|
||||||
|
func (e *Execution) APIClient() client.APIClient {
|
||||||
|
return e.client
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package environment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/integration-cli/fixtures/load"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type protectedElements struct {
|
||||||
|
images map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtectImage adds the specified image(s) to be protected in case of clean
|
||||||
|
func (e *Execution) ProtectImage(t testingT, images ...string) {
|
||||||
|
for _, image := range images {
|
||||||
|
e.protectedElements.images[image] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProtectedElements() protectedElements {
|
||||||
|
return protectedElements{
|
||||||
|
images: map[string]struct{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtectImages protects existing images and on linux frozen images from being
|
||||||
|
// cleaned up at the end of test runs
|
||||||
|
func ProtectImages(t testingT, testEnv *Execution) {
|
||||||
|
images := getExistingImages(t, testEnv)
|
||||||
|
|
||||||
|
if testEnv.DaemonInfo.OSType == "linux" {
|
||||||
|
images = append(images, ensureFrozenImagesLinux(t, testEnv)...)
|
||||||
|
}
|
||||||
|
testEnv.ProtectImage(t, images...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getExistingImages(t testingT, testEnv *Execution) []string {
|
||||||
|
client := testEnv.APIClient()
|
||||||
|
filter := filters.NewArgs()
|
||||||
|
filter.Add("dangling", "false")
|
||||||
|
imageList, err := client.ImageList(context.Background(), types.ImageListOptions{
|
||||||
|
Filters: filter,
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "failed to list images")
|
||||||
|
|
||||||
|
images := []string{}
|
||||||
|
for _, image := range imageList {
|
||||||
|
images = append(images, tagsFromImageSummary(image)...)
|
||||||
|
}
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagsFromImageSummary(image types.ImageSummary) []string {
|
||||||
|
result := []string{}
|
||||||
|
for _, tag := range image.RepoTags {
|
||||||
|
if tag != "<none>:<none>" {
|
||||||
|
result = append(result, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, digest := range image.RepoDigests {
|
||||||
|
if digest != "<none>@<none>" {
|
||||||
|
result = append(result, digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureFrozenImagesLinux(t testingT, testEnv *Execution) []string {
|
||||||
|
images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
|
||||||
|
err := load.FrozenImagesLinux(testEnv.APIClient(), images...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to load frozen images: %s", err)
|
||||||
|
}
|
||||||
|
return images
|
||||||
|
}
|
Loading…
Reference in New Issue