Add initial integration tests framework
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
This commit is contained in:
parent
9992d2e1bd
commit
cb69ab45ee
5 changed files with 662 additions and 3 deletions
23
Makefile
23
Makefile
|
@ -1,5 +1,7 @@
|
||||||
BUILDTAGS=
|
BUILDTAGS=
|
||||||
|
|
||||||
|
PROJECT=github.com/docker/containerd
|
||||||
|
|
||||||
GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null || true)
|
GIT_COMMIT := $(shell git rev-parse HEAD 2> /dev/null || true)
|
||||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2> /dev/null)
|
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2> /dev/null)
|
||||||
|
|
||||||
|
@ -13,8 +15,12 @@ ifeq ($(INTERACTIVE), 1)
|
||||||
DOCKER_FLAGS += -t
|
DOCKER_FLAGS += -t
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
TEST_ARTIFACTS_DIR := integration-test/test-artifacts
|
||||||
|
BUNDLE_ARCHIVES_DIR := $(TEST_ARTIFACTS_DIR)/archives
|
||||||
|
|
||||||
DOCKER_IMAGE := containerd-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
DOCKER_IMAGE := containerd-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH))
|
||||||
DOCKER_RUN := docker run --rm -i $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
|
DOCKER_RUN := docker run --privileged --rm -i $(DOCKER_FLAGS) "$(DOCKER_IMAGE)"
|
||||||
|
|
||||||
|
|
||||||
export GOPATH:=$(CURDIR)/vendor:$(GOPATH)
|
export GOPATH:=$(CURDIR)/vendor:$(GOPATH)
|
||||||
|
|
||||||
|
@ -46,7 +52,13 @@ shim: bin
|
||||||
shim-static:
|
shim-static:
|
||||||
cd containerd-shim && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd-shim
|
cd containerd-shim && go build -ldflags "-w -extldflags -static ${LDFLAGS}" -tags "$(BUILDTAGS)" -o ../bin/containerd-shim
|
||||||
|
|
||||||
dbuild:
|
$(BUNDLE_ARCHIVES_DIR)/busybox.tar:
|
||||||
|
@mkdir -p $(BUNDLE_ARCHIVES_DIR)
|
||||||
|
curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' -o $(BUNDLE_ARCHIVES_DIR)/busybox.tar
|
||||||
|
|
||||||
|
bundles-rootfs: $(BUNDLE_ARCHIVES_DIR)/busybox.tar
|
||||||
|
|
||||||
|
dbuild: $(BUNDLE_ARCHIVES_DIR)/busybox.tar
|
||||||
@docker build --rm --force-rm -t "$(DOCKER_IMAGE)" .
|
@docker build --rm --force-rm -t "$(DOCKER_IMAGE)" .
|
||||||
|
|
||||||
dtest: dbuild
|
dtest: dbuild
|
||||||
|
@ -68,7 +80,12 @@ shell: dbuild
|
||||||
$(DOCKER_RUN) bash
|
$(DOCKER_RUN) bash
|
||||||
|
|
||||||
test: all validate
|
test: all validate
|
||||||
go test -v $(shell go list ./... | grep -v /vendor)
|
go test -v $(shell go list ./... | grep -v /vendor | grep -v /integration-test)
|
||||||
|
ifneq ($(wildcard /.dockerenv), )
|
||||||
|
$(MAKE) install bundles-rootfs
|
||||||
|
cd integration-test ; \
|
||||||
|
go test -check.v $(TESTFLAGS) github.com/docker/containerd/integration-test
|
||||||
|
endif
|
||||||
|
|
||||||
validate: fmt
|
validate: fmt
|
||||||
|
|
||||||
|
|
107
integration-test/bundle_utils_test.go
Normal file
107
integration-test/bundle_utils_test.go
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
ocs "github.com/opencontainers/specs/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bundlesDir = filepath.Join("test-artifacts", "oci-bundles")
|
||||||
|
refOciSpecsPath = filepath.Join(bundlesDir, "config.json")
|
||||||
|
)
|
||||||
|
|
||||||
|
type OciProcessArgs struct {
|
||||||
|
Cmd string
|
||||||
|
Args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bundle struct {
|
||||||
|
Source string
|
||||||
|
Name string
|
||||||
|
Spec ocs.Spec
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var bundleMap map[string]Bundle
|
||||||
|
|
||||||
|
// untarRootfs untars the given `source` tarPath into `destination/rootfs`
|
||||||
|
func untarRootfs(source string, destination string) error {
|
||||||
|
destination = filepath.Join(destination, "rootfs")
|
||||||
|
if err := os.MkdirAll(destination, 0755); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tar := exec.Command("tar", "-C", destination, "-xf", source)
|
||||||
|
return tar.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBundleWithFilter generate a new oci-bundle named `name` from
|
||||||
|
// the provide `source` rootfs. It starts from the default spec
|
||||||
|
// generated by `runc spec`, overrides the `spec.Process.Args` value
|
||||||
|
// with `args` and set `spec.Process.Terminal` to false. It then apply
|
||||||
|
// `filter()` to the resulting spec if it is provided.
|
||||||
|
func CreateBundleWithFilter(source, name string, args []string, filter func(spec *ocs.Spec)) error {
|
||||||
|
// Generate the spec
|
||||||
|
var spec ocs.Spec
|
||||||
|
if f, err := os.Open(refOciSpecsPath); err != nil {
|
||||||
|
return fmt.Errorf("Failed to open default spec: %v", err)
|
||||||
|
} else {
|
||||||
|
if err := json.NewDecoder(f).Decode(&spec); err != nil {
|
||||||
|
return fmt.Errorf("Failed to load default spec: %v", err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.Process.Args = args
|
||||||
|
spec.Process.Terminal = false
|
||||||
|
if filter != nil {
|
||||||
|
filter(&spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
bundlePath := filepath.Join(bundlesDir, name)
|
||||||
|
nb := Bundle{source, name, spec, bundlePath}
|
||||||
|
|
||||||
|
// Check that we don't already have such a bundle
|
||||||
|
if b, ok := bundleMap[name]; ok {
|
||||||
|
if reflect.DeepEqual(b, nb) == false {
|
||||||
|
return fmt.Errorf("A bundle name named '%s' already exist but with different properties! %#v != %#v",
|
||||||
|
name, b, nb)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing should be there, but just in case
|
||||||
|
os.RemoveAll(bundlePath)
|
||||||
|
|
||||||
|
if err := untarRootfs(filepath.Join(archivesDir, source+".tar"), bundlePath); err != nil {
|
||||||
|
return fmt.Errorf("Failed to untar %s.tar: %v", source, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a place for the io fifo
|
||||||
|
if err := os.Mkdir(filepath.Join(bundlePath, "io"), 0755); err != nil {
|
||||||
|
return fmt.Errorf("Failed to create bundle io directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the updated spec to the right location
|
||||||
|
config, e := os.Create(filepath.Join(bundlePath, "config.json"))
|
||||||
|
if e != nil {
|
||||||
|
return fmt.Errorf("Failed to create oci spec: %v", e)
|
||||||
|
}
|
||||||
|
defer config.Close()
|
||||||
|
|
||||||
|
if err := json.NewEncoder(config).Encode(&spec); err != nil {
|
||||||
|
return fmt.Errorf("Failed to encore oci spec: %v", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
bundleMap[name] = nb
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBusyboxBundle(name string, args []string) error {
|
||||||
|
return CreateBundleWithFilter("busybox", name, args, nil)
|
||||||
|
}
|
228
integration-test/check_test.go
Normal file
228
integration-test/check_test.go
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/grpclog"
|
||||||
|
|
||||||
|
"github.com/docker/containerd/api/grpc/types"
|
||||||
|
"github.com/go-check/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
outputDirFormat = filepath.Join("test-artifacts", "runs", "%s")
|
||||||
|
archivesDir = filepath.Join("test-artifacts", "archives")
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
check.TestingT(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
check.Suite(&ContainerdSuite{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerdSuite struct {
|
||||||
|
cwd string
|
||||||
|
outputDir string
|
||||||
|
logFile *os.File
|
||||||
|
cd *exec.Cmd
|
||||||
|
syncChild chan error
|
||||||
|
grpcClient types.APIClient
|
||||||
|
eventFiltersMutex sync.Mutex
|
||||||
|
eventFilters map[string]func(event *types.Event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClient returns a connection to the Suite containerd
|
||||||
|
func (cs *ContainerdSuite) getClient(socket string) error {
|
||||||
|
// reset the logger for grpc to log to dev/null so that it does not mess with our stdio
|
||||||
|
grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags))
|
||||||
|
dialOpts := []grpc.DialOption{grpc.WithInsecure()}
|
||||||
|
dialOpts = append(dialOpts,
|
||||||
|
grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
|
||||||
|
return net.DialTimeout("unix", addr, timeout)
|
||||||
|
},
|
||||||
|
))
|
||||||
|
conn, err := grpc.Dial(socket, dialOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cs.grpcClient = types.NewAPIClient(conn)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerdEventsHandler will process all events coming from
|
||||||
|
// containerd. If a filter as been register for a given container id
|
||||||
|
// via `SetContainerEventFilter()`, it will be invoked every time an
|
||||||
|
// event for that id is received
|
||||||
|
func (cs *ContainerdSuite) ContainerdEventsHandler(events types.API_EventsClient) {
|
||||||
|
timestamp := uint64(time.Now().Unix())
|
||||||
|
for {
|
||||||
|
e, err := events.Recv()
|
||||||
|
if err != nil {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
events, _ = cs.grpcClient.Events(context.Background(), &types.EventsRequest{Timestamp: timestamp})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
timestamp = e.Timestamp
|
||||||
|
cs.eventFiltersMutex.Lock()
|
||||||
|
if f, ok := cs.eventFilters[e.Id]; ok {
|
||||||
|
f(e)
|
||||||
|
if e.Type == "exit" && e.Pid == "init" {
|
||||||
|
delete(cs.eventFilters, e.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cs.eventFiltersMutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateReferencesSpecs invoke `runc spec` to produce the baseline
|
||||||
|
// specs from which all future bundle will be generated
|
||||||
|
func generateReferenceSpecs(destination string) error {
|
||||||
|
specs := exec.Command("runc", "spec")
|
||||||
|
specs.Dir = destination
|
||||||
|
return specs.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) SetUpSuite(c *check.C) {
|
||||||
|
bundleMap = make(map[string]Bundle)
|
||||||
|
cs.eventFilters = make(map[string]func(event *types.Event))
|
||||||
|
|
||||||
|
// Get our CWD
|
||||||
|
if cwd, err := os.Getwd(); err != nil {
|
||||||
|
c.Fatalf("Could not determine current working directory: %v", err)
|
||||||
|
} else {
|
||||||
|
cs.cwd = cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean old bundles
|
||||||
|
os.RemoveAll(bundlesDir)
|
||||||
|
|
||||||
|
// Ensure the oci bundles directory exists
|
||||||
|
if err := os.MkdirAll(bundlesDir, 0755); err != nil {
|
||||||
|
c.Fatalf("Failed to create bundles directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the reference spec
|
||||||
|
if err := generateReferenceSpecs(bundlesDir); err != nil {
|
||||||
|
c.Fatalf("Unable to generate OCI reference spec: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our output directory
|
||||||
|
od := fmt.Sprintf(outputDirFormat, time.Now().Format("2006-01-02_150405.000000"))
|
||||||
|
cdStateDir := fmt.Sprintf("%s/containerd-master", od)
|
||||||
|
if err := os.MkdirAll(cdStateDir, 0755); err != nil {
|
||||||
|
c.Fatalf("Unable to created output directory '%s': %v", cdStateDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cdGRPCSock := filepath.Join(od, "containerd-master", "containerd.sock")
|
||||||
|
cdLogFile := filepath.Join(od, "containerd-master", "containerd.log")
|
||||||
|
|
||||||
|
f, err := os.OpenFile(cdLogFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR|os.O_SYNC, 0777)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Failed to create master containerd log file: %v", err)
|
||||||
|
}
|
||||||
|
cs.logFile = f
|
||||||
|
|
||||||
|
cd := exec.Command("containerd", "--debug",
|
||||||
|
"--state-dir", cdStateDir,
|
||||||
|
"--listen", cdGRPCSock,
|
||||||
|
"--metrics-interval", "0m0s",
|
||||||
|
"--runtime-args", fmt.Sprintf("--root=%s", filepath.Join(cs.cwd, cdStateDir, "runc")),
|
||||||
|
)
|
||||||
|
cd.Stderr = f
|
||||||
|
cd.Stdout = f
|
||||||
|
|
||||||
|
if err := cd.Start(); err != nil {
|
||||||
|
c.Fatalf("Unable to start the master containerd: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.outputDir = od
|
||||||
|
cs.cd = cd
|
||||||
|
cs.syncChild = make(chan error)
|
||||||
|
if err := cs.getClient(cdGRPCSock); err != nil {
|
||||||
|
// Kill the daemon
|
||||||
|
cs.cd.Process.Kill()
|
||||||
|
c.Fatalf("Failed to connect to daemon: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Monitor events
|
||||||
|
events, err := cs.grpcClient.Events(context.Background(), &types.EventsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Could not register containerd event handler: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go cs.ContainerdEventsHandler(events)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
cs.syncChild <- cd.Wait()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) TearDownSuite(c *check.C) {
|
||||||
|
|
||||||
|
// tell containerd to stop
|
||||||
|
if cs.cd != nil {
|
||||||
|
cs.cd.Process.Signal(os.Interrupt)
|
||||||
|
|
||||||
|
done := false
|
||||||
|
for done == false {
|
||||||
|
select {
|
||||||
|
case err := <-cs.syncChild:
|
||||||
|
if err != nil {
|
||||||
|
c.Errorf("master containerd did not exit cleanly: %v", err)
|
||||||
|
}
|
||||||
|
done = true
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
fmt.Println("Timeout while waiting for containerd to exit, killing it!")
|
||||||
|
cs.cd.Process.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs.logFile != nil {
|
||||||
|
cs.logFile.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) SetContainerEventFilter(id string, filter func(event *types.Event)) {
|
||||||
|
cs.eventFiltersMutex.Lock()
|
||||||
|
cs.eventFilters[id] = filter
|
||||||
|
cs.eventFiltersMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) TearDownTest(c *check.C) {
|
||||||
|
ctrs, err := cs.ListRunningContainers()
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Unable to retrieve running containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill all containers that survived
|
||||||
|
for _, ctr := range ctrs {
|
||||||
|
ch := make(chan interface{})
|
||||||
|
cs.SetContainerEventFilter(ctr.Id, func(e *types.Event) {
|
||||||
|
if e.Type == "exit" && e.Pid == "init" {
|
||||||
|
ch <- nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := cs.KillContainer(ctr.Id); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to cleanup leftover test containers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
|
}
|
249
integration-test/container_utils_test.go
Normal file
249
integration-test/container_utils_test.go
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/containerd/api/grpc/types"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) ListRunningContainers() ([]*types.Container, error) {
|
||||||
|
resp, err := cs.grpcClient.State(context.Background(), &types.StateRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp.Containers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) SignalContainerProcess(id string, procId string, sig uint32) error {
|
||||||
|
_, err := cs.grpcClient.Signal(context.Background(), &types.SignalRequest{
|
||||||
|
Id: id,
|
||||||
|
Pid: procId,
|
||||||
|
Signal: sig,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) SignalContainer(id string, sig uint32) error {
|
||||||
|
return cs.SignalContainerProcess(id, "init", sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) KillContainer(id string) error {
|
||||||
|
return cs.SignalContainerProcess(id, "init", uint32(syscall.SIGKILL))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) PauseContainer(id string) error {
|
||||||
|
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
|
||||||
|
Id: id,
|
||||||
|
Pid: "init",
|
||||||
|
Status: "paused",
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) ResumeContainer(id string) error {
|
||||||
|
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
|
||||||
|
Id: id,
|
||||||
|
Pid: "init",
|
||||||
|
Status: "running",
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) GetContainerStats(id string) (*types.StatsResponse, error) {
|
||||||
|
stats, err := cs.grpcClient.Stats(context.Background(), &types.StatsRequest{
|
||||||
|
Id: id,
|
||||||
|
})
|
||||||
|
return stats, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type stdio struct {
|
||||||
|
stdin string
|
||||||
|
stdout string
|
||||||
|
stderr string
|
||||||
|
stdinf *os.File
|
||||||
|
stdoutf *os.File
|
||||||
|
stderrf *os.File
|
||||||
|
stdoutBuffer bytes.Buffer
|
||||||
|
stderrBuffer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
type containerProcess struct {
|
||||||
|
containerId string
|
||||||
|
pid string
|
||||||
|
bundle *Bundle
|
||||||
|
io stdio
|
||||||
|
eventsCh chan *types.Event
|
||||||
|
cs *ContainerdSuite
|
||||||
|
hasExited bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerProcess) openIo() (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
c.Cleanup()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.io.stdinf, err = os.OpenFile(c.io.stdin, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.io.stdoutf, err = os.OpenFile(c.io.stdout, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go io.Copy(&c.io.stdoutBuffer, c.io.stdoutf)
|
||||||
|
|
||||||
|
c.io.stderrf, err = os.OpenFile(c.io.stderr, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go io.Copy(&c.io.stderrBuffer, c.io.stderrf)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerProcess) GetNextEvent() *types.Event {
|
||||||
|
e := <-c.eventsCh
|
||||||
|
|
||||||
|
if e.Type == "exit" && e.Pid == c.pid {
|
||||||
|
c.Cleanup()
|
||||||
|
c.hasExited = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerProcess) CloseStdin() error {
|
||||||
|
_, err := c.cs.grpcClient.UpdateProcess(context.Background(), &types.UpdateProcessRequest{
|
||||||
|
Id: c.containerId,
|
||||||
|
Pid: c.pid,
|
||||||
|
CloseStdin: true,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *containerProcess) Cleanup() {
|
||||||
|
for _, f := range []*os.File{
|
||||||
|
c.io.stdinf,
|
||||||
|
c.io.stdoutf,
|
||||||
|
c.io.stderrf,
|
||||||
|
} {
|
||||||
|
if f != nil {
|
||||||
|
f.Close()
|
||||||
|
f = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContainerProcess(cs *ContainerdSuite, bundle *Bundle, cid, pid string) (c *containerProcess, err error) {
|
||||||
|
c = &containerProcess{
|
||||||
|
containerId: cid,
|
||||||
|
pid: "init",
|
||||||
|
bundle: bundle,
|
||||||
|
eventsCh: make(chan *types.Event, 8),
|
||||||
|
cs: cs,
|
||||||
|
hasExited: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, path := range map[string]*string{
|
||||||
|
"stdin": &c.io.stdin,
|
||||||
|
"stdout": &c.io.stdout,
|
||||||
|
"stderr": &c.io.stderr,
|
||||||
|
} {
|
||||||
|
*path = filepath.Join(bundle.Path, "io", cid+"-"+pid+"-"+name)
|
||||||
|
if err = syscall.Mkfifo(*path, 0755); err != nil && !os.IsExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.openIo(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) StartContainer(id, bundleName string) (c *containerProcess, err error) {
|
||||||
|
bundle, ok := bundleMap[bundleName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("No such bundle '%s'", bundleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err = NewContainerProcess(cs, &bundle, id, "init")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &types.CreateContainerRequest{
|
||||||
|
Id: id,
|
||||||
|
BundlePath: filepath.Join(cs.cwd, bundle.Path),
|
||||||
|
Stdin: filepath.Join(cs.cwd, c.io.stdin),
|
||||||
|
Stdout: filepath.Join(cs.cwd, c.io.stdout),
|
||||||
|
Stderr: filepath.Join(cs.cwd, c.io.stderr),
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.SetContainerEventFilter(id, func(event *types.Event) {
|
||||||
|
c.eventsCh <- event
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := cs.grpcClient.CreateContainer(context.Background(), r); err != nil {
|
||||||
|
c.Cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) RunContainer(id, bundleName string) (c *containerProcess, err error) {
|
||||||
|
c, err = cs.StartContainer(id, bundleName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
e := c.GetNextEvent()
|
||||||
|
if e.Type == "exit" && e.Pid == "init" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) AddProcessToContainer(init *containerProcess, pid, cwd string, env, args []string, uid, gid uint32) (c *containerProcess, err error) {
|
||||||
|
c, err = NewContainerProcess(cs, init.bundle, init.containerId, pid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pr := &types.AddProcessRequest{
|
||||||
|
Id: init.containerId,
|
||||||
|
Pid: pid,
|
||||||
|
Args: args,
|
||||||
|
Cwd: cwd,
|
||||||
|
Env: env,
|
||||||
|
User: &types.User{
|
||||||
|
Uid: uid,
|
||||||
|
Gid: gid,
|
||||||
|
},
|
||||||
|
Stdin: filepath.Join(cs.cwd, c.io.stdin),
|
||||||
|
Stdout: filepath.Join(cs.cwd, c.io.stdout),
|
||||||
|
Stderr: filepath.Join(cs.cwd, c.io.stderr),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cs.grpcClient.AddProcess(context.Background(), pr)
|
||||||
|
if err != nil {
|
||||||
|
c.Cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
58
integration-test/start_test.go
Normal file
58
integration-test/start_test.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/pkg/integration/checker"
|
||||||
|
"github.com/go-check/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) TestStartBusyboxLsSlash(t *check.C) {
|
||||||
|
expectedOutput := `bin
|
||||||
|
dev
|
||||||
|
etc
|
||||||
|
home
|
||||||
|
lib
|
||||||
|
lib64
|
||||||
|
linuxrc
|
||||||
|
media
|
||||||
|
mnt
|
||||||
|
opt
|
||||||
|
proc
|
||||||
|
root
|
||||||
|
run
|
||||||
|
sbin
|
||||||
|
sys
|
||||||
|
tmp
|
||||||
|
usr
|
||||||
|
var
|
||||||
|
`
|
||||||
|
if err := CreateBusyboxBundle("busybox-ls-slash", []string{"ls", "/"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := cs.RunContainer("myls", "busybox-ls-slash")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Assert(c.io.stdoutBuffer.String(), checker.Equals, expectedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) TestStartBusyboxNoSuchFile(t *check.C) {
|
||||||
|
expectedOutput := `oci runtime error: exec: \"NoSuchFile\": executable file not found in $PATH`
|
||||||
|
|
||||||
|
if err := CreateBusyboxBundle("busybox-NoSuchFile", []string{"NoSuchFile"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := cs.RunContainer("NoSuchFile", "busybox-NoSuchFile")
|
||||||
|
t.Assert(err.Error(), checker.Contains, expectedOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ContainerdSuite) TestStartBusyboxTop(t *check.C) {
|
||||||
|
if err := CreateBusyboxBundle("busybox-top", []string{"top"}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := cs.StartContainer("top", "busybox-top")
|
||||||
|
t.Assert(err, checker.Equals, nil)
|
||||||
|
}
|
Loading…
Reference in a new issue