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:
parent
43145ecf1b
commit
3f2333b667
|
@ -0,0 +1,2 @@
|
|||
// integration is used for integration testing of libcontainer
|
||||
package integration
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue