Add testing for linux factory Load

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2014-10-22 23:27:06 +00:00 committed by Victor Marmol
parent 7760faaab4
commit 926ab56ea8
9 changed files with 255 additions and 73 deletions

View File

@ -9,30 +9,30 @@ type ContainerInfo interface {
// Returns the current run state of the container.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// SystemError - System error.
RunState() (*RunState, Error)
// Systemerror - System error.
RunState() (*RunState, error)
// Returns the current config of the container.
Config() *Config
// Returns the PIDs inside this container. The PIDs are in the namespace of the calling process.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// SystemError - System error.
// Systemerror - System error.
//
// Some of the returned PIDs may no longer refer to processes in the Container, unless
// the Container state is PAUSED in which case every PID in the slice is valid.
Processes() ([]int, Error)
Processes() ([]int, error)
// Returns statistics for the container.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// SystemError - System error.
Stats() (*ContainerStats, Error)
// Systemerror - System error.
Stats() (*ContainerStats, error)
}
// A libcontainer container object.
@ -45,60 +45,60 @@ type Container interface {
// Start a process inside the container. Returns the PID of the new process (in the caller process's namespace) and a channel that will return the exit status of the process whenever it dies.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// ConfigInvalid - config is invalid,
// ContainerPaused - Container is paused,
// SystemError - System error.
StartProcess(config *ProcessConfig) (pid int, err Error)
// Systemerror - System error.
StartProcess(config *ProcessConfig) (pid int, err error)
// Destroys the container after killing all running processes.
//
// Any event registrations are removed before the container is destroyed.
// No error is returned if the container is already destroyed.
//
// Errors:
// SystemError - System error.
Destroy() Error
// errors:
// Systemerror - System error.
Destroy() error
// If the Container state is RUNNING or PAUSING, sets the Container state to PAUSING and pauses
// the execution of any user processes. Asynchronously, when the container finished being paused the
// state is changed to PAUSED.
// If the Container state is PAUSED, do nothing.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// SystemError - System error.
Pause() Error
// Systemerror - System error.
Pause() error
// If the Container state is PAUSED, resumes the execution of any user processes in the
// Container before setting the Container state to RUNNING.
// If the Container state is RUNNING, do nothing.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// SystemError - System error.
Resume() Error
// Systemerror - System error.
Resume() error
// Signal sends the specified signal to a process owned by the container.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// ContainerPaused - Container is paused,
// SystemError - System error.
Signal(pid, signal int) Error
// Systemerror - System error.
Signal(pid, signal int) error
// Wait waits for the init process of the conatiner to die and returns it's exit status.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// SystemError - System error.
Wait() (exitStatus int, err Error)
// Systemerror - System error.
Wait() (exitStatus int, err error)
// WaitProcess waits on a process owned by the container.
//
// Errors:
// errors:
// ContainerDestroyed - Container no longer exists,
// SystemError - System error.
WaitProcess(pid int) (exitStatus int, err Error)
// Systemerror - System error.
WaitProcess(pid int) (exitStatus int, err error)
}

View File

@ -1,5 +1,7 @@
package libcontainer
import "io"
// API error code type.
type ErrorCode int
@ -10,7 +12,7 @@ const (
InvalidIdFormat
// Container errors
ContainerDestroyed
ContainerNotExists
ContainerPaused
// Common errors
@ -24,14 +26,14 @@ func (c ErrorCode) String() string {
return "Id already in use"
case InvalidIdFormat:
return "Invalid format"
case ContainerDestroyed:
return "Container destroyed"
case ContainerPaused:
return "Container paused"
case ConfigInvalid:
return "Invalid configuration"
case SystemError:
return "System Error"
return "System error"
case ContainerNotExists:
return "Container does not exist"
default:
return "Unknown error"
}
@ -44,7 +46,7 @@ type Error interface {
// Returns a verbose string including the error message
// and a representation of the stack trace suitable for
// printing.
Detail() string
Detail(w io.Writer) error
// Returns the error code for this error.
Code() ErrorCode

20
error_test.go Normal file
View File

@ -0,0 +1,20 @@
package libcontainer
import "testing"
func TestErrorCode(t *testing.T) {
codes := map[ErrorCode]string{
IdInUse: "Id already in use",
InvalidIdFormat: "Invalid format",
ContainerPaused: "Container paused",
ConfigInvalid: "Invalid configuration",
SystemError: "System error",
ContainerNotExists: "Container does not exist",
}
for code, expected := range codes {
if actual := code.String(); actual != expected {
t.Fatalf("expected string %q but received %q", expected, actual)
}
}
}

View File

@ -10,21 +10,21 @@ type Factory interface {
//
// Returns the new container with a running process.
//
// Errors:
// errors:
// IdInUse - id is already in use by a container
// InvalidIdFormat - id has incorrect format
// ConfigInvalid - config is invalid
// SystemError - System error
// Systemerror - System error
//
// On error, any partially created container parts are cleaned up (the operation is atomic).
Create(id string, config *Config) (Container, Error)
Create(id string, config *Config) (Container, error)
// Load takes an ID for an existing container and returns the container information
// from the state. This presents a read only view of the container.
//
// Errors:
// errors:
// Path does not exist
// Container is stopped
// System error
Load(id string) (ContainerInfo, Error)
Load(id string) (ContainerInfo, error)
}

View File

@ -1,46 +1,48 @@
package libcontainer
import (
"bytes"
"fmt"
"runtime"
"io"
"text/template"
"time"
"github.com/docker/libcontainer/stacktrace"
)
var newLine = []byte("\n")
var errorTemplate = template.Must(template.New("error").Parse(`Timestamp: {{.Timestamp}}
Code: {{.ECode}}
Message: {{.Err.Error}}
Frames:{{range $i, $frame := .Stack.Frames}}
---
{{$i}}: {{$frame.Function}}
Package: {{$frame.Package}}
File: {{$frame.File}}{{end}}
`))
func newGenericError(err error, c ErrorCode) Error {
return &GenericError{
timestamp: time.Now(),
err: err,
code: c,
stack: captureStackTrace(2),
Timestamp: time.Now(),
Err: err,
ECode: c,
Stack: stacktrace.Capture(2),
}
}
func captureStackTrace(skip int) string {
buf := make([]byte, 4096)
buf = buf[:runtime.Stack(buf, true)]
lines := bytes.Split(buf, newLine)
return string(bytes.Join(lines[skip:], newLine))
}
type GenericError struct {
timestamp time.Time
code ErrorCode
err error
stack string
Timestamp time.Time
ECode ErrorCode
Err error
Stack stacktrace.Stacktrace
}
func (e *GenericError) Error() string {
return fmt.Sprintf("[%d] %s: %s", e.code, e.code, e.err)
return fmt.Sprintf("[%d] %s: %s", e.ECode, e.ECode, e.Err)
}
func (e *GenericError) Code() ErrorCode {
return e.code
return e.ECode
}
func (e *GenericError) Detail() string {
return fmt.Sprintf("[%d] %s\n%s", e.code, e.err, e.stack)
func (e *GenericError) Detail(w io.Writer) error {
return errorTemplate.Execute(w, e)
}

14
generic_error_test.go Normal file
View File

@ -0,0 +1,14 @@
package libcontainer
import (
"fmt"
"io/ioutil"
"testing"
)
func TestErrorDetail(t *testing.T) {
err := newGenericError(fmt.Errorf("test error"), SystemError)
if derr := err.Detail(ioutil.Discard); derr != nil {
t.Fatal(derr)
}
}

View File

@ -23,11 +23,11 @@ func (c *linuxContainer) Config() *Config {
return c.config
}
func (c *linuxContainer) RunState() (*RunState, Error) {
func (c *linuxContainer) RunState() (*RunState, error) {
panic("not implemented")
}
func (c *linuxContainer) Processes() ([]int, Error) {
func (c *linuxContainer) Processes() ([]int, error) {
var (
err error
pids []int
@ -44,7 +44,7 @@ func (c *linuxContainer) Processes() ([]int, Error) {
return pids, nil
}
func (c *linuxContainer) Stats() (*ContainerStats, Error) {
func (c *linuxContainer) Stats() (*ContainerStats, error) {
var (
err error
stats = &ContainerStats{}

View File

@ -14,7 +14,7 @@ const (
)
// New returns a linux based container factory based in the root directory.
func New(root string) (Factory, Error) {
func New(root string) (Factory, error) {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, newGenericError(err, SystemError)
}
@ -30,11 +30,11 @@ type linuxFactory struct {
root string
}
func (l *linuxFactory) Create(id string, config *Config) (Container, Error) {
func (l *linuxFactory) Create(id string, config *Config) (Container, error) {
panic("not implemented")
}
func (l *linuxFactory) Load(id string) (ContainerInfo, Error) {
func (l *linuxFactory) Load(id string) (ContainerInfo, error) {
containerRoot := filepath.Join(l.root, id)
config, err := l.loadContainerConfig(containerRoot)
if err != nil {
@ -54,11 +54,11 @@ func (l *linuxFactory) Load(id string) (ContainerInfo, Error) {
}, nil
}
func (l *linuxFactory) loadContainerConfig(root string) (*Config, Error) {
func (l *linuxFactory) loadContainerConfig(root string) (*Config, error) {
f, err := os.Open(filepath.Join(root, configFilename))
if err != nil {
if os.IsNotExist(err) {
return nil, newGenericError(err, ContainerDestroyed)
return nil, newGenericError(err, ContainerNotExists)
}
return nil, newGenericError(err, SystemError)
}
@ -71,11 +71,11 @@ func (l *linuxFactory) loadContainerConfig(root string) (*Config, Error) {
return config, nil
}
func (l *linuxFactory) loadContainerState(root string) (*State, Error) {
func (l *linuxFactory) loadContainerState(root string) (*State, error) {
f, err := os.Open(filepath.Join(root, stateFilename))
if err != nil {
if os.IsNotExist(err) {
return nil, newGenericError(err, ContainerDestroyed)
return nil, newGenericError(err, ContainerNotExists)
}
return nil, newGenericError(err, SystemError)
}

144
linux_factory_test.go Normal file
View File

@ -0,0 +1,144 @@
// +build linux
package libcontainer
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func newTestRoot() (string, error) {
dir, err := ioutil.TempDir("", "libcontainer")
if err != nil {
return "", err
}
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
}
return dir, nil
}
func TestFactoryNew(t *testing.T) {
root, rerr := newTestRoot()
if rerr != nil {
t.Fatal(rerr)
}
defer os.RemoveAll(root)
factory, err := New(root)
if err != nil {
t.Fatal(err)
}
if factory == nil {
t.Fatal("factory should not be nil")
}
lfactory, ok := factory.(*linuxFactory)
if !ok {
t.Fatal("expected linux factory returned on linux based systems")
}
if lfactory.root != root {
t.Fatalf("expected factory root to be %q but received %q", root, lfactory.root)
}
}
func TestFactoryLoadNotExists(t *testing.T) {
root, rerr := newTestRoot()
if rerr != nil {
t.Fatal(rerr)
}
defer os.RemoveAll(root)
factory, err := New(root)
if err != nil {
t.Fatal(err)
}
_, err = factory.Load("nocontainer")
if err == nil {
t.Fatal("expected nil error loading non-existing container")
}
lerr, ok := err.(Error)
if !ok {
t.Fatal("expected libcontainer error type")
}
if lerr.Code() != ContainerNotExists {
t.Fatalf("expected error code %s but received %s", ContainerNotExists, lerr.Code())
}
}
func TestFactoryLoadContainer(t *testing.T) {
root, err := newTestRoot()
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(root)
// setup default container config and state for mocking
var (
id = "1"
expectedConfig = &Config{
RootFs: "/mycontainer/root",
}
expectedState = &State{
InitPid: 1024,
}
)
if err := os.Mkdir(filepath.Join(root, id), 0700); err != nil {
t.Fatal(err)
}
if err := marshal(filepath.Join(root, id, configFilename), expectedConfig); err != nil {
t.Fatal(err)
}
if err := marshal(filepath.Join(root, id, stateFilename), expectedState); err != nil {
t.Fatal(err)
}
factory, err := New(root)
if err != nil {
t.Fatal(err)
}
container, err := factory.Load(id)
if err != nil {
t.Fatal(err)
}
if container.ID() != id {
t.Fatalf("expected container id %q but received %q", id, container.ID())
}
config := container.Config()
if config == nil {
t.Fatal("expected non nil container config")
}
if config.RootFs != expectedConfig.RootFs {
t.Fatalf("expected rootfs %q but received %q", expectedConfig.RootFs, config.RootFs)
}
lcontainer, ok := container.(*linuxContainer)
if !ok {
t.Fatal("expected linux container on linux based systems")
}
if lcontainer.state.InitPid != expectedState.InitPid {
t.Fatalf("expected init pid %d but received %d", expectedState.InitPid, lcontainer.state.InitPid)
}
}
func marshal(path string, v interface{}) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return json.NewEncoder(f).Encode(v)
}