Add testing for linux factory Load
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
7760faaab4
commit
926ab56ea8
60
container.go
60
container.go
|
@ -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)
|
||||
}
|
||||
|
|
12
error.go
12
error.go
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
10
factory.go
10
factory.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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{}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue