Add integration test pkg

This integration pkg adds a framework for writing integration tests
aginst the libcontainer APIs

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2014-10-17 00:00:59 +00:00
parent 43145ecf1b
commit 3f2333b667
6 changed files with 253 additions and 0 deletions

2
integration/doc.go Normal file
View File

@ -0,0 +1,2 @@
// integration is used for integration testing of libcontainer
package integration

38
integration/exec_test.go Normal file
View File

@ -0,0 +1,38 @@
package integration
import (
"strings"
"testing"
)
func TestExecPS(t *testing.T) {
if testing.Short() {
return
}
rootfs, err := newRootFs()
if err != nil {
t.Fatal(err)
}
defer remove(rootfs)
config := newTemplateConfig(rootfs)
buffers, exitCode, err := runContainer(config, "", "ps")
if err != nil {
t.Fatal(err)
}
if exitCode != 0 {
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
}
lines := strings.Split(buffers.Stdout.String(), "\n")
if len(lines) < 2 {
t.Fatalf("more than one process running for output %q", buffers.Stdout.String())
}
expected := `1 root ps`
actual := strings.Trim(lines[1], "\n ")
if actual != expected {
t.Fatalf("expected output %q but received %q", expected, actual)
}
}

39
integration/init_test.go Normal file
View File

@ -0,0 +1,39 @@
package integration
import (
"log"
"os"
"runtime"
"github.com/docker/libcontainer/namespaces"
"github.com/docker/libcontainer/syncpipe"
)
// init runs the libcontainer initialization code because of the busybox style needs
// to work around the go runtime and the issues with forking
func init() {
if len(os.Args) < 2 || os.Args[1] != "init" {
return
}
runtime.LockOSThread()
container, err := loadConfig()
if err != nil {
log.Fatal(err)
}
rootfs, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
if err != nil {
log.Fatalf("unable to create sync pipe: %s", err)
}
if err := namespaces.Init(container, rootfs, "", syncPipe, os.Args[3:]); err != nil {
log.Fatalf("unable to initialize for container: %s", err)
}
os.Exit(1)
}

View File

@ -0,0 +1,64 @@
package integration
import (
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/devices"
)
// newTemplateConfig returns a base template for running a container
//
// it uses a network strategy of just setting a loopback interface
// and the default setup for devices
func newTemplateConfig(rootfs string) *libcontainer.Config {
return &libcontainer.Config{
RootFs: rootfs,
Tty: false,
Capabilities: []string{
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE",
},
Namespaces: map[string]bool{
"NEWNS": true,
"NEWUTS": true,
"NEWIPC": true,
"NEWPID": true,
"NEWNET": true,
},
Cgroups: &cgroups.Cgroup{
Parent: "integration",
AllowAllDevices: false,
AllowedDevices: devices.DefaultAllowedDevices,
},
MountConfig: &libcontainer.MountConfig{
DeviceNodes: devices.DefaultAutoCreatedDevices,
},
Hostname: "integration",
Env: []string{
"HOME=/root",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=integration",
"TERM=xterm",
},
Networks: []*libcontainer.Network{
{
Type: "loopback",
Address: "127.0.0.1/0",
Gateway: "localhost",
},
},
}
}

95
integration/utils_test.go Normal file
View File

@ -0,0 +1,95 @@
package integration
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/namespaces"
)
func newStdBuffers() *stdBuffers {
return &stdBuffers{
Stdin: bytes.NewBuffer(nil),
Stdout: bytes.NewBuffer(nil),
Stderr: bytes.NewBuffer(nil),
}
}
type stdBuffers struct {
Stdin *bytes.Buffer
Stdout *bytes.Buffer
Stderr *bytes.Buffer
}
func writeConfig(config *libcontainer.Config) error {
f, err := os.OpenFile(filepath.Join(config.RootFs, "container.json"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700)
if err != nil {
return err
}
defer f.Close()
return json.NewEncoder(f).Encode(config)
}
func loadConfig() (*libcontainer.Config, error) {
f, err := os.Open(filepath.Join(os.Getenv("data_path"), "container.json"))
if err != nil {
return nil, err
}
defer f.Close()
var container *libcontainer.Config
if err := json.NewDecoder(f).Decode(&container); err != nil {
return nil, err
}
return container, nil
}
// newRootFs creates a new tmp directory and copies the busybox root filesystem
func newRootFs() (string, error) {
dir, err := ioutil.TempDir("", "")
if err != nil {
return "", err
}
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
}
if err := copyBusybox(dir); err != nil {
return "", nil
}
return dir, nil
}
func remove(dir string) {
os.RemoveAll(dir)
}
// copyBusybox copies the rootfs for a busybox container created for the test image
// into the new directory for the specific test
func copyBusybox(dest string) error {
out, err := exec.Command("sh", "-c", fmt.Sprintf("cp -R /busybox/* %s/", dest)).CombinedOutput()
if err != nil {
return fmt.Errorf("copy error %q: %q", err, out)
}
return nil
}
// runContainer runs the container with the specific config and arguments
//
// buffers are returned containing the STDOUT and STDERR output for the run
// along with the exit code and any go error
func runContainer(config *libcontainer.Config, console string, args ...string) (buffers *stdBuffers, exitCode int, err error) {
if err := writeConfig(config); err != nil {
return nil, -1, err
}
buffers = newStdBuffers()
exitCode, err = namespaces.Exec(config, buffers.Stdin, buffers.Stdout, buffers.Stderr,
console, config.RootFs, args, namespaces.DefaultCreateCommand, nil)
return
}

15
utils/utils_test.go Normal file
View File

@ -0,0 +1,15 @@
package utils
import "testing"
func TestGenerateName(t *testing.T) {
name, err := GenerateRandomName("veth", 5)
if err != nil {
t.Fatal(err)
}
expected := 5 + len("veth")
if len(name) != 5+len("veth") {
t.Fatalf("expected name to be %d chars but received %d", expected, len(name))
}
}