Merge pull request #381 from crosbymichael/configfactory

Add functional API for Factory configuration
This commit is contained in:
Michael Crosby 2015-02-16 11:44:47 -08:00
commit 55d61e22c5
10 changed files with 158 additions and 124 deletions

View File

@ -1,39 +0,0 @@
package manager
import (
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/docker/libcontainer/configs"
)
// Create a new cgroup manager with specified configuration
// TODO this object is not really initialized until Apply() is called.
// Maybe make this to the equivalent of Apply() at some point?
// @vmarmol
func NewCgroupManager(cgroups *configs.Cgroup) cgroups.Manager {
if systemd.UseSystemd() {
return &systemd.Manager{
Cgroups: cgroups,
}
}
return &fs.Manager{
Cgroups: cgroups,
}
}
// Restore a cgroup manager with specified configuration and state
func LoadCgroupManager(cgroups *configs.Cgroup, paths map[string]string) cgroups.Manager {
if systemd.UseSystemd() {
return &systemd.Manager{
Cgroups: cgroups,
Paths: paths,
}
}
return &fs.Manager{
Cgroups: cgroups,
Paths: paths,
}
}

View File

@ -236,7 +236,7 @@ func TestEnter(t *testing.T) {
config := newTemplateConfig(rootfs)
factory, err := libcontainer.New(root, []string{os.Args[0], "init", "--"})
factory, err := libcontainer.New(root, libcontainer.InitArgs(os.Args[0], "init", "--"), libcontainer.Cgroupfs)
if err != nil {
t.Fatal(err)
}
@ -340,7 +340,7 @@ func TestFreeze(t *testing.T) {
config := newTemplateConfig(rootfs)
factory, err := libcontainer.New(root, []string{os.Args[0], "init", "--"})
factory, err := libcontainer.New(root, libcontainer.InitArgs(os.Args[0], "init", "--"), libcontainer.Cgroupfs)
if err != nil {
t.Fatal(err)
}

View File

@ -17,7 +17,7 @@ func init() {
}
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
factory, err := libcontainer.New("", nil)
factory, err := libcontainer.New("")
if err != nil {
log.Fatalf("unable to initialize for container: %s", err)
}

View File

@ -81,7 +81,7 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe
Stderr: buffers.Stderr,
}
factory, err := libcontainer.New(".", []string{os.Args[0], "init", "--"})
factory, err := libcontainer.New(".", libcontainer.InitArgs(os.Args[0], "init", "--"), libcontainer.Cgroupfs)
if err != nil {
return nil, -1, err
}

View File

@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"sync"
"syscall"
"github.com/docker/libcontainer/cgroups"
@ -22,6 +23,7 @@ type linuxContainer struct {
cgroupManager cgroups.Manager
initArgs []string
initProcess parentProcess
m sync.Mutex
}
// ID returns the container's unique ID
@ -35,46 +37,15 @@ func (c *linuxContainer) Config() configs.Config {
}
func (c *linuxContainer) Status() (Status, error) {
if c.initProcess == nil {
return Destroyed, nil
}
// return Running if the init process is alive
if err := syscall.Kill(c.initProcess.pid(), 0); err != nil {
if err == syscall.ESRCH {
return Destroyed, nil
}
return 0, newSystemError(err)
}
if c.config.Cgroups != nil && c.config.Cgroups.Freezer == configs.Frozen {
return Paused, nil
}
return Running, nil
c.m.Lock()
defer c.m.Unlock()
return c.currentStatus()
}
func (c *linuxContainer) State() (*State, error) {
status, err := c.Status()
if err != nil {
return nil, err
}
if status == Destroyed {
return nil, newGenericError(fmt.Errorf("container destroyed"), ContainerNotExists)
}
startTime, err := c.initProcess.startTime()
if err != nil {
return nil, newSystemError(err)
}
state := &State{
ID: c.ID(),
Config: *c.config,
InitProcessPid: c.initProcess.pid(),
InitProcessStartTime: startTime,
CgroupPaths: c.cgroupManager.GetPaths(),
NamespacePaths: make(map[string]string),
}
for _, ns := range c.config.Namespaces {
state.NamespacePaths[string(ns.Type)] = ns.GetPath(c.initProcess.pid())
}
return state, nil
c.m.Lock()
defer c.m.Unlock()
return c.currentState()
}
func (c *linuxContainer) Processes() ([]int, error) {
@ -109,7 +80,9 @@ func (c *linuxContainer) Stats() (*Stats, error) {
}
func (c *linuxContainer) Start(process *Process) (int, error) {
status, err := c.Status()
c.m.Lock()
defer c.m.Unlock()
status, err := c.currentStatus()
if err != nil {
return -1, err
}
@ -126,6 +99,7 @@ func (c *linuxContainer) Start(process *Process) (int, error) {
return -1, newSystemError(err)
}
if doInit {
c.updateState(parent)
}
return parent.pid(), nil
@ -222,7 +196,9 @@ func newPipe() (parent *os.File, child *os.File, err error) {
}
func (c *linuxContainer) Destroy() error {
status, err := c.Status()
c.m.Lock()
defer c.m.Unlock()
status, err := c.currentStatus()
if err != nil {
return err
}
@ -243,14 +219,20 @@ func (c *linuxContainer) Destroy() error {
}
func (c *linuxContainer) Pause() error {
c.m.Lock()
defer c.m.Unlock()
return c.cgroupManager.Freeze(configs.Frozen)
}
func (c *linuxContainer) Resume() error {
c.m.Lock()
defer c.m.Unlock()
return c.cgroupManager.Freeze(configs.Thawed)
}
func (c *linuxContainer) Signal(signal os.Signal) error {
c.m.Lock()
defer c.m.Unlock()
if c.initProcess == nil {
return newGenericError(nil, ContainerNotRunning)
}
@ -263,7 +245,7 @@ func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
func (c *linuxContainer) updateState(process parentProcess) error {
c.initProcess = process
state, err := c.State()
state, err := c.currentState()
if err != nil {
return err
}
@ -274,3 +256,46 @@ func (c *linuxContainer) updateState(process parentProcess) error {
defer f.Close()
return json.NewEncoder(f).Encode(state)
}
func (c *linuxContainer) currentStatus() (Status, error) {
if c.initProcess == nil {
return Destroyed, nil
}
// return Running if the init process is alive
if err := syscall.Kill(c.initProcess.pid(), 0); err != nil {
if err == syscall.ESRCH {
return Destroyed, nil
}
return 0, newSystemError(err)
}
if c.config.Cgroups != nil && c.config.Cgroups.Freezer == configs.Frozen {
return Paused, nil
}
return Running, nil
}
func (c *linuxContainer) currentState() (*State, error) {
status, err := c.currentStatus()
if err != nil {
return nil, err
}
if status == Destroyed {
return nil, newGenericError(fmt.Errorf("container destroyed"), ContainerNotExists)
}
startTime, err := c.initProcess.startTime()
if err != nil {
return nil, newSystemError(err)
}
state := &State{
ID: c.ID(),
Config: *c.config,
InitProcessPid: c.initProcess.pid(),
InitProcessStartTime: startTime,
CgroupPaths: c.cgroupManager.GetPaths(),
NamespacePaths: make(map[string]string),
}
for _, ns := range c.config.Namespaces {
state.NamespacePaths[string(ns.Type)] = ns.GetPath(c.initProcess.pid())
}
return state, nil
}

View File

@ -10,9 +10,9 @@ import (
"path/filepath"
"regexp"
"github.com/golang/glog"
cgroups "github.com/docker/libcontainer/cgroups/manager"
"github.com/docker/libcontainer/cgroups"
"github.com/docker/libcontainer/cgroups/fs"
"github.com/docker/libcontainer/cgroups/systemd"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/configs/validate"
)
@ -26,39 +26,89 @@ var (
maxIdLen = 1024
)
// New returns a linux based container factory based in the root directory.
func New(root string, initArgs []string) (Factory, error) {
// InitArgs returns an options func to configure a LinuxFactory with the
// provided init arguments.
func InitArgs(args ...string) func(*LinuxFactory) error {
return func(l *LinuxFactory) error {
l.InitArgs = args
return nil
}
}
// SystemdCgroups is an options func to configure a LinuxFactory to return
// containers that use systemd to create and manage cgroups.
func SystemdCgroups(l *LinuxFactory) error {
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
return &systemd.Manager{
Cgroups: config,
Paths: paths,
}
}
return nil
}
// Cgroupfs is an options func to configure a LinuxFactory to return
// containers that use the native cgroups filesystem implementation to
// create and manage cgroups.
func Cgroupfs(l *LinuxFactory) error {
l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
return &fs.Manager{
Cgroups: config,
Paths: paths,
}
}
return nil
}
// New returns a linux based container factory based in the root directory and
// configures the factory with the provided option funcs.
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
if root != "" {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, newGenericError(err, SystemError)
}
}
return &linuxFactory{
root: root,
initArgs: initArgs,
validator: validate.New(),
}, nil
l := &LinuxFactory{
Root: root,
InitArgs: []string{os.Args[0], "init"},
Validator: validate.New(),
}
Cgroupfs(l)
for _, opt := range options {
if err := opt(l); err != nil {
return nil, err
}
}
return l, nil
}
// linuxFactory implements the default factory interface for linux based systems.
type linuxFactory struct {
// root is the root directory
root string
initArgs []string
validator validate.Validator
// LinuxFactory implements the default factory interface for linux based systems.
type LinuxFactory struct {
// Root directory for the factory to store state.
Root string
// InitArgs are arguments for calling the init responsibilities for spawning
// a container.
InitArgs []string
// Validator provides validation to container configurations.
Validator validate.Validator
// NewCgroupsManager returns an initialized cgroups manager for a single container.
NewCgroupsManager func(config *configs.Cgroup, paths map[string]string) cgroups.Manager
}
func (l *linuxFactory) Create(id string, config *configs.Config) (Container, error) {
if l.root == "" {
func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
if l.Root == "" {
return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid)
}
if err := l.validateID(id); err != nil {
return nil, err
}
if err := l.validator.Validate(config); err != nil {
if err := l.Validator.Validate(config); err != nil {
return nil, newGenericError(err, ConfigInvalid)
}
containerRoot := filepath.Join(l.root, id)
containerRoot := filepath.Join(l.Root, id)
if _, err := os.Stat(containerRoot); err == nil {
return nil, newGenericError(fmt.Errorf("Container with id exists: %v", id), IdInUse)
} else if !os.IsNotExist(err) {
@ -71,16 +121,16 @@ func (l *linuxFactory) Create(id string, config *configs.Config) (Container, err
id: id,
root: containerRoot,
config: config,
initArgs: l.initArgs,
cgroupManager: cgroups.NewCgroupManager(config.Cgroups),
initArgs: l.InitArgs,
cgroupManager: l.NewCgroupsManager(config.Cgroups, nil),
}, nil
}
func (l *linuxFactory) Load(id string) (Container, error) {
if l.root == "" {
func (l *LinuxFactory) Load(id string) (Container, error) {
if l.Root == "" {
return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid)
}
containerRoot := filepath.Join(l.root, id)
containerRoot := filepath.Join(l.Root, id)
state, err := l.loadState(containerRoot)
if err != nil {
return nil, err
@ -89,21 +139,19 @@ func (l *linuxFactory) Load(id string) (Container, error) {
processPid: state.InitProcessPid,
processStartTime: state.InitProcessStartTime,
}
cgroupManager := cgroups.LoadCgroupManager(state.Config.Cgroups, state.CgroupPaths)
glog.Infof("using %s as cgroup manager", cgroupManager)
return &linuxContainer{
initProcess: r,
id: id,
config: &state.Config,
initArgs: l.initArgs,
cgroupManager: cgroupManager,
initArgs: l.InitArgs,
cgroupManager: l.NewCgroupsManager(state.Config.Cgroups, state.CgroupPaths),
root: containerRoot,
}, nil
}
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
// This is a low level implementation detail of the reexec and should not be consumed externally
func (l *linuxFactory) StartInitialization(pipefd uintptr) (err error) {
func (l *LinuxFactory) StartInitialization(pipefd uintptr) (err error) {
var (
pipe = os.NewFile(uintptr(pipefd), "pipe")
it = initType(os.Getenv("_LIBCONTAINER_INITTYPE"))
@ -134,7 +182,7 @@ func (l *linuxFactory) StartInitialization(pipefd uintptr) (err error) {
return i.Init()
}
func (l *linuxFactory) loadState(root string) (*State, error) {
func (l *LinuxFactory) loadState(root string) (*State, error) {
f, err := os.Open(filepath.Join(root, stateFilename))
if err != nil {
if os.IsNotExist(err) {
@ -150,7 +198,7 @@ func (l *linuxFactory) loadState(root string) (*State, error) {
return state, nil
}
func (l *linuxFactory) validateID(id string) error {
func (l *LinuxFactory) validateID(id string) error {
if !idRegex.MatchString(id) {
return newGenericError(fmt.Errorf("Invalid id format: %v", id), InvalidIdFormat)
}

View File

@ -29,19 +29,19 @@ func TestFactoryNew(t *testing.T) {
t.Fatal(rerr)
}
defer os.RemoveAll(root)
factory, err := New(root, nil)
factory, err := New(root, Cgroupfs)
if err != nil {
t.Fatal(err)
}
if factory == nil {
t.Fatal("factory should not be nil")
}
lfactory, ok := factory.(*linuxFactory)
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)
if lfactory.Root != root {
t.Fatalf("expected factory root to be %q but received %q", root, lfactory.Root)
}
}
@ -51,7 +51,7 @@ func TestFactoryLoadNotExists(t *testing.T) {
t.Fatal(rerr)
}
defer os.RemoveAll(root)
factory, err := New(root, nil)
factory, err := New(root, Cgroupfs)
if err != nil {
t.Fatal(err)
}
@ -91,7 +91,7 @@ func TestFactoryLoadContainer(t *testing.T) {
if err := marshal(filepath.Join(root, id, stateFilename), expectedState); err != nil {
t.Fatal(err)
}
factory, err := New(root, nil)
factory, err := New(root, Cgroupfs)
if err != nil {
t.Fatal(err)
}

View File

@ -15,7 +15,7 @@ var initCommand = cli.Command{
Action: func(context *cli.Context) {
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
factory, err := libcontainer.New("", nil)
factory, err := libcontainer.New("")
if err != nil {
log.Fatal(err)
}

View File

@ -10,7 +10,7 @@ import (
func main() {
app := cli.NewApp()
app.Name = "nsinit"
app.Version = "1"
app.Version = "2"
app.Author = "libcontainer maintainers"
app.Flags = []cli.Flag{
cli.StringFlag{Name: "nspid"},

View File

@ -29,7 +29,7 @@ func loadConfig(context *cli.Context) (*configs.Config, error) {
}
func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
return libcontainer.New(context.GlobalString("root"), []string{os.Args[0], "init"})
return libcontainer.New(context.GlobalString("root"), libcontainer.Cgroupfs)
}
func getContainer(context *cli.Context) (libcontainer.Container, error) {