Introduce a environment package in integration-cli

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2016-12-25 20:28:38 +01:00
parent 4d5cba127b
commit 433e2e8a1e
No known key found for this signature in database
GPG Key ID: 083CC6FD6EB699A3
20 changed files with 311 additions and 211 deletions

View File

@ -4,27 +4,136 @@ import (
"fmt"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
"testing"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/swarm"
cliconfig "github.com/docker/docker/cli/config"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/integration-cli/environment"
"github.com/docker/docker/pkg/reexec"
"github.com/go-check/check"
)
func Test(t *testing.T) {
const (
// the private registry to use for tests
privateRegistryURL = "127.0.0.1:5000"
// path to containerd's ctr binary
ctrBinary = "docker-containerd-ctr"
// the docker daemon binary to use
dockerdBinary = "dockerd"
)
var (
testEnv *environment.Execution
// FIXME(vdemeester) remove these and use environmentdaemonPid
protectedImages = map[string]struct{}{}
// the docker client binary to use
dockerBinary = "docker"
// isLocalDaemon is true if the daemon under test is on the same
// host as the CLI.
isLocalDaemon bool
// daemonPlatform is held globally so that tests can make intelligent
// decisions on how to configure themselves according to the platform
// of the daemon. This is initialized in docker_utils by sending
// a version call to the daemon and examining the response header.
daemonPlatform string
// WindowsBaseImage is the name of the base image for Windows testing
// Environment variable WINDOWS_BASE_IMAGE can override this
WindowsBaseImage 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
// 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.
daemonStorageDriver string
// isolation is the isolation mode of the daemon under test
isolation container.Isolation
// experimentalDaemon tell whether the main daemon has
// experimental features enabled or not
experimentalDaemon bool
daemonKernelVersion string
)
func init() {
var err error
reexec.Init() // This is required for external graphdriver tests
testEnv, err = environment.New()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
assignGlobalVariablesFromTestEnv(testEnv)
}
// FIXME(vdemeester) remove this and use environment
func assignGlobalVariablesFromTestEnv(testEnv *environment.Execution) {
isLocalDaemon = testEnv.LocalDaemon()
daemonPlatform = testEnv.DaemonPlatform()
dockerBasePath = testEnv.DockerBasePath()
volumesConfigPath = testEnv.VolumesConfigPath()
containerStoragePath = testEnv.ContainerStoragePath()
daemonStorageDriver = testEnv.DaemonStorageDriver()
isolation = testEnv.Isolation()
experimentalDaemon = testEnv.ExperimentalDaemon()
daemonKernelVersion = testEnv.DaemonKernelVersion()
WindowsBaseImage = testEnv.MinimalBaseImage()
}
func TestMain(m *testing.M) {
var err error
if dockerBin := os.Getenv("DOCKER_BINARY"); dockerBin != "" {
dockerBinary = dockerBin
}
dockerBinary, err = exec.LookPath(dockerBinary)
if err != nil {
fmt.Printf("ERROR: couldn't resolve full path to the Docker binary (%v)\n", err)
os.Exit(1)
}
cmd := exec.Command(dockerBinary, "images", "-f", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}")
cmd.Env = appendBaseEnv(true)
out, err := cmd.CombinedOutput()
if err != nil {
panic(fmt.Errorf("err=%v\nout=%s\n", err, out))
}
images := strings.Split(strings.TrimSpace(string(out)), "\n")
for _, img := range images {
protectedImages[img] = struct{}{}
}
if !isLocalDaemon {
fmt.Println("INFO: Testing against a remote daemon")
} else {
fmt.Println("INFO: Testing against a local daemon")
}
exitCode := m.Run()
os.Exit(exitCode)
}
func Test(t *testing.T) {
if daemonPlatform == "linux" {
ensureFrozenImagesLinux(t)
}
@ -39,8 +148,8 @@ type DockerSuite struct {
}
func (s *DockerSuite) OnTimeout(c *check.C) {
if daemonPid > 0 && isLocalDaemon {
daemon.SignalDaemonDump(daemonPid)
if testEnv.DaemonPID() > 0 && isLocalDaemon {
daemon.SignalDaemonDump(testEnv.DaemonPID())
}
}

View File

@ -324,7 +324,7 @@ func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
func (d *Daemon) StartWithBusybox(t testingT, arg ...string) {
d.Start(t, arg...)
if err := d.LoadBusybox(); err != nil {
t.Fatalf("Error loading busybox image to current daeom: %s", d.id)
t.Fatalf("Error loading busybox image to current daemon: %s\n%v", d.id, err)
}
}

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration-cli/environment"
"github.com/docker/docker/pkg/integration/checker"
icmd "github.com/docker/docker/pkg/integration/cmd"
"github.com/go-check/check"
@ -212,7 +213,7 @@ func (s *DockerSuite) TestInspectBindMountPoint(c *check.C) {
if daemonPlatform == "windows" {
modifier = ""
// TODO Windows: Temporary check - remove once TP5 support is dropped
if windowsDaemonKV < 14350 {
if environment.WindowsKernelVersion(testEnv.DaemonKernelVersion()) < 14350 {
c.Skip("Needs later Windows build for RO volumes")
}
// Linux creates the host directory if it doesn't exist. Windows does not.

View File

@ -1,142 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/reexec"
)
const (
// the private registry to use for tests
privateRegistryURL = "127.0.0.1:5000"
// the docker daemon binary to use
dockerdBinary = "dockerd"
)
var (
// the docker client binary to use
dockerBinary = "docker"
// path to containerd's ctr binary
ctrBinary = "docker-containerd-ctr"
// isLocalDaemon is true if the daemon under test is on the same
// host as the CLI.
isLocalDaemon bool
// daemonPlatform is held globally so that tests can make intelligent
// decisions on how to configure themselves according to the platform
// of the daemon. This is initialized in docker_utils by sending
// a version call to the daemon and examining the response header.
daemonPlatform string
// windowsDaemonKV is used on Windows to distinguish between different
// versions. This is necessary to enable certain tests based on whether
// the platform supports it. For example, Windows Server 2016 TP3 did
// not support volumes, but TP4 did.
windowsDaemonKV int
// 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
// experimentalDaemon tell whether the main daemon has
// experimental features enabled or not
experimentalDaemon bool
// 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.
daemonStorageDriver string
// WindowsBaseImage is the name of the base image for Windows testing
// Environment variable WINDOWS_BASE_IMAGE can override this
WindowsBaseImage = "microsoft/windowsservercore"
// isolation is the isolation mode of the daemon under test
isolation container.Isolation
// daemonPid is the pid of the main test daemon
daemonPid int
daemonKernelVersion string
)
func init() {
reexec.Init()
if dockerBin := os.Getenv("DOCKER_BINARY"); dockerBin != "" {
dockerBinary = dockerBin
}
var err error
dockerBinary, err = exec.LookPath(dockerBinary)
if err != nil {
fmt.Printf("ERROR: couldn't resolve full path to the Docker binary (%v)\n", err)
os.Exit(1)
}
// 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 {
isLocalDaemon = false
} else {
isLocalDaemon = true
}
// TODO Windows CI. This are incorrect and need fixing into
// platform specific pieces.
// This is only used for a tests with local daemon true (Linux-only today)
// default is "/var/lib/docker", but we'll try and ask the
// /info endpoint for the specific root dir
dockerBasePath = "/var/lib/docker"
type Info struct {
DockerRootDir string
ExperimentalBuild bool
KernelVersion string
}
var i Info
status, b, err := sockRequest("GET", "/info", nil)
if err == nil && status == 200 {
if err = json.Unmarshal(b, &i); err == nil {
dockerBasePath = i.DockerRootDir
experimentalDaemon = i.ExperimentalBuild
daemonKernelVersion = i.KernelVersion
}
}
volumesConfigPath = dockerBasePath + "/volumes"
containerStoragePath = dockerBasePath + "/containers"
if len(os.Getenv("WINDOWS_BASE_IMAGE")) > 0 {
WindowsBaseImage = os.Getenv("WINDOWS_BASE_IMAGE")
fmt.Println("INFO: Windows Base image is ", WindowsBaseImage)
}
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)
}
}
}

View File

@ -25,7 +25,6 @@ import (
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/integration"
"github.com/docker/docker/pkg/integration/checker"
icmd "github.com/docker/docker/pkg/integration/cmd"
@ -34,60 +33,6 @@ import (
"github.com/go-check/check"
)
func init() {
cmd := exec.Command(dockerBinary, "images", "-f", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}")
cmd.Env = appendBaseEnv(true)
out, err := cmd.CombinedOutput()
if err != nil {
panic(fmt.Errorf("err=%v\nout=%s\n", err, out))
}
images := strings.Split(strings.TrimSpace(string(out)), "\n")
for _, img := range images {
protectedImages[img] = struct{}{}
}
res, body, err := sockRequestRaw("GET", "/info", nil, "application/json")
if err != nil {
panic(fmt.Errorf("Init failed to get /info: %v", err))
}
defer body.Close()
if res.StatusCode != http.StatusOK {
panic(fmt.Errorf("Init failed to get /info. Res=%v", res))
}
svrHeader, _ := httputils.ParseServerHeader(res.Header.Get("Server"))
daemonPlatform = svrHeader.OS
if daemonPlatform != "linux" && daemonPlatform != "windows" {
panic("Cannot run tests against platform: " + daemonPlatform)
}
// Now we know the daemon platform, can set paths used by tests.
var info types.Info
err = json.NewDecoder(body).Decode(&info)
if err != nil {
panic(fmt.Errorf("Init failed to unmarshal docker info: %v", err))
}
daemonStorageDriver = info.Driver
dockerBasePath = info.DockerRootDir
volumesConfigPath = filepath.Join(dockerBasePath, "volumes")
containerStoragePath = filepath.Join(dockerBasePath, "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)
// On Windows, extract out the version as we need to make selective
// decisions during integration testing as and when features are implemented.
// e.g. in "10.0 10550 (10550.1000.amd64fre.branch.date-time)" we want 10550
windowsDaemonKV, _ = strconv.Atoi(strings.Split(info.KernelVersion, " ")[1])
} else {
volumesConfigPath = strings.Replace(volumesConfigPath, `\`, `/`, -1)
containerStoragePath = strings.Replace(containerStoragePath, `\`, `/`, -1)
}
isolation = info.Isolation
}
func daemonHost() string {
daemonURLStr := "unix://" + opts.DefaultUnixSocket
if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
@ -292,8 +237,6 @@ func getAllVolumes() ([]*types.Volume, error) {
return volumes.Volumes, nil
}
var protectedImages = map[string]struct{}{}
func deleteAllImages(c *check.C) {
cmd := exec.Command(dockerBinary, "images", "--digests")
cmd.Env = appendBaseEnv(true)
@ -1265,10 +1208,7 @@ func runSleepingContainerInImage(c *check.C, image string, extraArgs ...string)
// minimalBaseImage returns the name of the minimal base image for the current
// daemon platform.
func minimalBaseImage() string {
if daemonPlatform == "windows" {
return WindowsBaseImage
}
return "scratch"
return testEnv.MinimalBaseImage()
}
func getGoroutineNumber() (int, error) {

View File

@ -0,0 +1,184 @@
package environment
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
// Execution holds informations about the test execution environment.
type Execution struct {
daemonPlatform string
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
}
// New creates a new Execution struct
func New() (*Execution, error) {
localDaemon := true
// 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 {
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)
}
}
return &Execution{
localDaemon: localDaemon,
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,
}, nil
}
func getDaemonDockerInfo() (types.Info, error) {
// FIXME(vdemeester) should be safe to use as is
client, err := client.NewEnvClient()
if err != nil {
return types.Info{}, err
}
return client.Info(context.Background())
}
// LocalDaemon is true if the daemon under test is on the same
// host as the CLI.
func (e *Execution) LocalDaemon() bool {
return e.localDaemon
}
// DaemonPlatform is held globally so that tests can make intelligent
// decisions on how to configure themselves according to the platform
// 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) DaemonPlatform() string {
return e.daemonPlatform
}
// 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)
func (e *Execution) MinimalBaseImage() string {
return e.baseImage
}
// DaemonKernelVersion is the kernel version of the daemon
func (e *Execution) DaemonKernelVersion() string {
return e.daemonKernelVersion
}
// WindowsKernelVersion is used on Windows to distinguish between different
// versions. This is necessary to enable certain tests based on whether
// the platform supports it. For example, Windows Server 2016 TP3 did
// not support volumes, but TP4 did.
func WindowsKernelVersion(kernelVersion string) int {
winKV, _ := strconv.Atoi(strings.Split(kernelVersion, " ")[1])
return winKV
}

View File

@ -9,18 +9,26 @@ import (
"runtime"
"strings"
"sync"
"testing"
"github.com/docker/docker/integration-cli/fixtures/load"
"github.com/docker/docker/pkg/integration/checker"
"github.com/go-check/check"
)
func ensureFrozenImagesLinux(t *testing.T) {
type testingT interface {
logT
Fatalf(string, ...interface{})
}
type logT interface {
Logf(string, ...interface{})
}
func ensureFrozenImagesLinux(t testingT) {
images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
err := load.FrozenImagesLinux(dockerBinary, images...)
if err != nil {
t.Log(dockerCmdWithError("images"))
t.Logf(dockerCmdWithError("images"))
t.Fatalf("%+v", err)
}
for _, img := range images {