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. // Returns the current run state of the container.
// //
// Errors: // errors:
// ContainerDestroyed - Container no longer exists, // ContainerDestroyed - Container no longer exists,
// SystemError - System error. // Systemerror - System error.
RunState() (*RunState, Error) RunState() (*RunState, error)
// Returns the current config of the container. // Returns the current config of the container.
Config() *Config Config() *Config
// Returns the PIDs inside this container. The PIDs are in the namespace of the calling process. // Returns the PIDs inside this container. The PIDs are in the namespace of the calling process.
// //
// Errors: // errors:
// ContainerDestroyed - Container no longer exists, // 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 // 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. // 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. // Returns statistics for the container.
// //
// Errors: // errors:
// ContainerDestroyed - Container no longer exists, // ContainerDestroyed - Container no longer exists,
// SystemError - System error. // Systemerror - System error.
Stats() (*ContainerStats, Error) Stats() (*ContainerStats, error)
} }
// A libcontainer container object. // 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. // 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, // ContainerDestroyed - Container no longer exists,
// ConfigInvalid - config is invalid, // ConfigInvalid - config is invalid,
// ContainerPaused - Container is paused, // ContainerPaused - Container is paused,
// SystemError - System error. // Systemerror - System error.
StartProcess(config *ProcessConfig) (pid int, err Error) StartProcess(config *ProcessConfig) (pid int, err error)
// Destroys the container after killing all running processes. // Destroys the container after killing all running processes.
// //
// Any event registrations are removed before the container is destroyed. // Any event registrations are removed before the container is destroyed.
// No error is returned if the container is already destroyed. // No error is returned if the container is already destroyed.
// //
// Errors: // errors:
// SystemError - System error. // Systemerror - System error.
Destroy() Error Destroy() error
// If the Container state is RUNNING or PAUSING, sets the Container state to PAUSING and pauses // 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 // the execution of any user processes. Asynchronously, when the container finished being paused the
// state is changed to PAUSED. // state is changed to PAUSED.
// If the Container state is PAUSED, do nothing. // If the Container state is PAUSED, do nothing.
// //
// Errors: // errors:
// ContainerDestroyed - Container no longer exists, // ContainerDestroyed - Container no longer exists,
// SystemError - System error. // Systemerror - System error.
Pause() Error Pause() error
// If the Container state is PAUSED, resumes the execution of any user processes in the // If the Container state is PAUSED, resumes the execution of any user processes in the
// Container before setting the Container state to RUNNING. // Container before setting the Container state to RUNNING.
// If the Container state is RUNNING, do nothing. // If the Container state is RUNNING, do nothing.
// //
// Errors: // errors:
// ContainerDestroyed - Container no longer exists, // ContainerDestroyed - Container no longer exists,
// SystemError - System error. // Systemerror - System error.
Resume() Error Resume() error
// Signal sends the specified signal to a process owned by the container. // Signal sends the specified signal to a process owned by the container.
// //
// Errors: // errors:
// ContainerDestroyed - Container no longer exists, // ContainerDestroyed - Container no longer exists,
// ContainerPaused - Container is paused, // ContainerPaused - Container is paused,
// SystemError - System error. // Systemerror - System error.
Signal(pid, signal int) Error Signal(pid, signal int) error
// Wait waits for the init process of the conatiner to die and returns it's exit status. // Wait waits for the init process of the conatiner to die and returns it's exit status.
// //
// Errors: // errors:
// ContainerDestroyed - Container no longer exists, // ContainerDestroyed - Container no longer exists,
// SystemError - System error. // Systemerror - System error.
Wait() (exitStatus int, err Error) Wait() (exitStatus int, err error)
// WaitProcess waits on a process owned by the container. // WaitProcess waits on a process owned by the container.
// //
// Errors: // errors:
// ContainerDestroyed - Container no longer exists, // ContainerDestroyed - Container no longer exists,
// SystemError - System error. // Systemerror - System error.
WaitProcess(pid int) (exitStatus int, err Error) WaitProcess(pid int) (exitStatus int, err error)
} }

View File

@ -1,5 +1,7 @@
package libcontainer package libcontainer
import "io"
// API error code type. // API error code type.
type ErrorCode int type ErrorCode int
@ -10,7 +12,7 @@ const (
InvalidIdFormat InvalidIdFormat
// Container errors // Container errors
ContainerDestroyed ContainerNotExists
ContainerPaused ContainerPaused
// Common errors // Common errors
@ -24,14 +26,14 @@ func (c ErrorCode) String() string {
return "Id already in use" return "Id already in use"
case InvalidIdFormat: case InvalidIdFormat:
return "Invalid format" return "Invalid format"
case ContainerDestroyed:
return "Container destroyed"
case ContainerPaused: case ContainerPaused:
return "Container paused" return "Container paused"
case ConfigInvalid: case ConfigInvalid:
return "Invalid configuration" return "Invalid configuration"
case SystemError: case SystemError:
return "System Error" return "System error"
case ContainerNotExists:
return "Container does not exist"
default: default:
return "Unknown error" return "Unknown error"
} }
@ -44,7 +46,7 @@ type Error interface {
// Returns a verbose string including the error message // Returns a verbose string including the error message
// and a representation of the stack trace suitable for // and a representation of the stack trace suitable for
// printing. // printing.
Detail() string Detail(w io.Writer) error
// Returns the error code for this error. // Returns the error code for this error.
Code() ErrorCode 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. // Returns the new container with a running process.
// //
// Errors: // errors:
// IdInUse - id is already in use by a container // IdInUse - id is already in use by a container
// InvalidIdFormat - id has incorrect format // InvalidIdFormat - id has incorrect format
// ConfigInvalid - config is invalid // ConfigInvalid - config is invalid
// SystemError - System error // Systemerror - System error
// //
// On error, any partially created container parts are cleaned up (the operation is atomic). // 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 // 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. // from the state. This presents a read only view of the container.
// //
// Errors: // errors:
// Path does not exist // Path does not exist
// Container is stopped // Container is stopped
// System error // System error
Load(id string) (ContainerInfo, Error) Load(id string) (ContainerInfo, error)
} }

View File

@ -1,46 +1,48 @@
package libcontainer package libcontainer
import ( import (
"bytes"
"fmt" "fmt"
"runtime" "io"
"text/template"
"time" "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 { func newGenericError(err error, c ErrorCode) Error {
return &GenericError{ return &GenericError{
timestamp: time.Now(), Timestamp: time.Now(),
err: err, Err: err,
code: c, ECode: c,
stack: captureStackTrace(2), 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 { type GenericError struct {
timestamp time.Time Timestamp time.Time
code ErrorCode ECode ErrorCode
err error Err error
stack string Stack stacktrace.Stacktrace
} }
func (e *GenericError) Error() string { 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 { func (e *GenericError) Code() ErrorCode {
return e.code return e.ECode
} }
func (e *GenericError) Detail() string { func (e *GenericError) Detail(w io.Writer) error {
return fmt.Sprintf("[%d] %s\n%s", e.code, e.err, e.stack) 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 return c.config
} }
func (c *linuxContainer) RunState() (*RunState, Error) { func (c *linuxContainer) RunState() (*RunState, error) {
panic("not implemented") panic("not implemented")
} }
func (c *linuxContainer) Processes() ([]int, Error) { func (c *linuxContainer) Processes() ([]int, error) {
var ( var (
err error err error
pids []int pids []int
@ -44,7 +44,7 @@ func (c *linuxContainer) Processes() ([]int, Error) {
return pids, nil return pids, nil
} }
func (c *linuxContainer) Stats() (*ContainerStats, Error) { func (c *linuxContainer) Stats() (*ContainerStats, error) {
var ( var (
err error err error
stats = &ContainerStats{} stats = &ContainerStats{}

View File

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