Merge pull request #299 from avagin/api-linux
Prepare ground for moving on new API
This commit is contained in:
commit
c44e63a62d
1
Makefile
1
Makefile
|
@ -12,6 +12,7 @@ sh:
|
||||||
GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune -o -wholename ./.git -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u)
|
GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune -o -wholename ./.git -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u)
|
||||||
|
|
||||||
direct-test:
|
direct-test:
|
||||||
|
go get github.com/golang/glog && \
|
||||||
go test $(TEST_TAGS) -cover -v $(GO_PACKAGES)
|
go test $(TEST_TAGS) -cover -v $(GO_PACKAGES)
|
||||||
|
|
||||||
direct-test-short:
|
direct-test-short:
|
||||||
|
|
3
error.go
3
error.go
|
@ -14,6 +14,7 @@ const (
|
||||||
// Container errors
|
// Container errors
|
||||||
ContainerNotExists
|
ContainerNotExists
|
||||||
ContainerPaused
|
ContainerPaused
|
||||||
|
ContainerNotStopped
|
||||||
|
|
||||||
// Common errors
|
// Common errors
|
||||||
ConfigInvalid
|
ConfigInvalid
|
||||||
|
@ -34,6 +35,8 @@ func (c ErrorCode) String() string {
|
||||||
return "System error"
|
return "System error"
|
||||||
case ContainerNotExists:
|
case ContainerNotExists:
|
||||||
return "Container does not exist"
|
return "Container does not exist"
|
||||||
|
case ContainerNotStopped:
|
||||||
|
return "Container isn't stopped"
|
||||||
default:
|
default:
|
||||||
return "Unknown error"
|
return "Unknown error"
|
||||||
}
|
}
|
||||||
|
|
10
factory.go
10
factory.go
|
@ -27,4 +27,14 @@ type Factory interface {
|
||||||
// Container is stopped
|
// Container is stopped
|
||||||
// System error
|
// System error
|
||||||
Load(id string) (Container, error)
|
Load(id string) (Container, error)
|
||||||
|
|
||||||
|
// StartInitialization is an internal API to libcontainer used during the rexec of the
|
||||||
|
// container. pipefd is the fd to the child end of the pipe used to syncronize the
|
||||||
|
// parent and child process providing state and configuration to the child process and
|
||||||
|
// returning any errors during the init of the container
|
||||||
|
//
|
||||||
|
// Errors:
|
||||||
|
// pipe connection error
|
||||||
|
// system error
|
||||||
|
StartInitialization(pipefd uintptr) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,13 @@
|
||||||
package libcontainer
|
package libcontainer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/docker/libcontainer/network"
|
"github.com/docker/libcontainer/network"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
@ -13,6 +20,7 @@ type linuxContainer struct {
|
||||||
config *Config
|
config *Config
|
||||||
state *State
|
state *State
|
||||||
cgroupManager CgroupManager
|
cgroupManager CgroupManager
|
||||||
|
initArgs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) ID() string {
|
func (c *linuxContainer) ID() string {
|
||||||
|
@ -24,7 +32,7 @@ func (c *linuxContainer) Config() *Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) RunState() (RunState, error) {
|
func (c *linuxContainer) RunState() (RunState, error) {
|
||||||
panic("not implemented")
|
return Destroyed, nil // FIXME return a real state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) Processes() ([]int, error) {
|
func (c *linuxContainer) Processes() ([]int, error) {
|
||||||
|
@ -53,13 +61,91 @@ func (c *linuxContainer) Stats() (*ContainerStats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) StartProcess(config *ProcessConfig) (int, error) {
|
func (c *linuxContainer) StartProcess(config *ProcessConfig) (int, error) {
|
||||||
|
state, err := c.RunState()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != Destroyed {
|
||||||
glog.Info("start new container process")
|
glog.Info("start new container process")
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.startInitProcess(config); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.state.InitPid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *linuxContainer) updateStateFile() error {
|
||||||
|
data, err := json.MarshalIndent(c.state, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return newGenericError(err, SystemError)
|
||||||
|
}
|
||||||
|
|
||||||
|
fnew := filepath.Join(c.root, fmt.Sprintf("%s.new", stateFilename))
|
||||||
|
f, err := os.Create(fnew)
|
||||||
|
if err != nil {
|
||||||
|
return newGenericError(err, SystemError)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return newGenericError(err, SystemError)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
fname := filepath.Join(c.root, stateFilename)
|
||||||
|
if err := os.Rename(fnew, fname); err != nil {
|
||||||
|
return newGenericError(err, SystemError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *linuxContainer) startInitProcess(config *ProcessConfig) error {
|
||||||
|
cmd := exec.Command(c.initArgs[0], append(c.initArgs[1:], config.Args...)...)
|
||||||
|
cmd.Stdin = config.Stdin
|
||||||
|
cmd.Stdout = config.Stdout
|
||||||
|
cmd.Stderr = config.Stderr
|
||||||
|
|
||||||
|
cmd.Env = config.Env
|
||||||
|
cmd.Dir = c.config.RootFs
|
||||||
|
|
||||||
|
if cmd.SysProcAttr == nil {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
|
||||||
|
|
||||||
|
//FIXME call namespaces.Exec()
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.state.InitPid = cmd.Process.Pid
|
||||||
|
err := c.updateStateFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) Destroy() error {
|
func (c *linuxContainer) Destroy() error {
|
||||||
glog.Info("destroy container")
|
state, err := c.RunState()
|
||||||
panic("not implemented")
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if state != Destroyed {
|
||||||
|
return newGenericError(nil, ContainerNotStopped)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.RemoveAll(c.root)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) Pause() error {
|
func (c *linuxContainer) Pause() error {
|
||||||
|
|
|
@ -23,13 +23,16 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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, initArgs []string) (Factory, error) {
|
||||||
|
if root != "" {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &linuxFactory{
|
return &linuxFactory{
|
||||||
root: root,
|
root: root,
|
||||||
|
initArgs: initArgs,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,9 +40,13 @@ func New(root string) (Factory, error) {
|
||||||
type linuxFactory struct {
|
type linuxFactory struct {
|
||||||
// root is the root directory
|
// root is the root directory
|
||||||
root string
|
root string
|
||||||
|
initArgs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linuxFactory) Create(id string, config *Config) (Container, error) {
|
func (l *linuxFactory) Create(id string, config *Config) (Container, error) {
|
||||||
|
if l.root == "" {
|
||||||
|
return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid)
|
||||||
|
}
|
||||||
if !idRegex.MatchString(id) {
|
if !idRegex.MatchString(id) {
|
||||||
return nil, newGenericError(fmt.Errorf("Invalid id format: %v", id), InvalidIdFormat)
|
return nil, newGenericError(fmt.Errorf("Invalid id format: %v", id), InvalidIdFormat)
|
||||||
}
|
}
|
||||||
|
@ -56,10 +63,43 @@ func (l *linuxFactory) Create(id string, config *Config) (Container, error) {
|
||||||
return nil, newGenericError(err, SystemError)
|
return nil, newGenericError(err, SystemError)
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("not implemented")
|
data, err := json.MarshalIndent(config, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return nil, newGenericError(err, SystemError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(containerRoot, 0700); err != nil {
|
||||||
|
return nil, newGenericError(err, SystemError)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(filepath.Join(containerRoot, configFilename))
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(containerRoot)
|
||||||
|
return nil, newGenericError(err, SystemError)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
_, err = f.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(containerRoot)
|
||||||
|
return nil, newGenericError(err, SystemError)
|
||||||
|
}
|
||||||
|
|
||||||
|
cgroupManager := NewCgroupManager()
|
||||||
|
return &linuxContainer{
|
||||||
|
id: id,
|
||||||
|
root: containerRoot,
|
||||||
|
config: config,
|
||||||
|
initArgs: l.initArgs,
|
||||||
|
state: &State{},
|
||||||
|
cgroupManager: cgroupManager,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linuxFactory) Load(id string) (Container, error) {
|
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)
|
||||||
glog.Infof("loading container config from %s", containerRoot)
|
glog.Infof("loading container config from %s", containerRoot)
|
||||||
config, err := l.loadContainerConfig(containerRoot)
|
config, err := l.loadContainerConfig(containerRoot)
|
||||||
|
@ -81,6 +121,7 @@ func (l *linuxFactory) Load(id string) (Container, error) {
|
||||||
config: config,
|
config: config,
|
||||||
state: state,
|
state: state,
|
||||||
cgroupManager: cgroupManager,
|
cgroupManager: cgroupManager,
|
||||||
|
initArgs: l.initArgs,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,3 +158,11 @@ func (l *linuxFactory) loadContainerState(root string) (*State, error) {
|
||||||
}
|
}
|
||||||
return state, nil
|
return state, 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 (f *linuxFactory) StartInitialization(pipefd uintptr) (err error) {
|
||||||
|
|
||||||
|
/* FIXME call namespaces.Init() */
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ func TestFactoryNew(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
factory, err := New(root)
|
factory, err := New(root, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func TestFactoryLoadNotExists(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
factory, err := New(root)
|
factory, err := New(root, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ func TestFactoryLoadContainer(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
factory, err := New(root)
|
factory, err := New(root, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
all:
|
||||||
|
go build -o nsinit .
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
@ -17,6 +18,12 @@ import (
|
||||||
"github.com/docker/libcontainer/namespaces"
|
"github.com/docker/libcontainer/namespaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dataPath = os.Getenv("data_path")
|
||||||
|
console = os.Getenv("console")
|
||||||
|
rawPipeFd = os.Getenv("pipe")
|
||||||
|
)
|
||||||
|
|
||||||
var execCommand = cli.Command{
|
var execCommand = cli.Command{
|
||||||
Name: "exec",
|
Name: "exec",
|
||||||
Usage: "execute a new command inside a container",
|
Usage: "execute a new command inside a container",
|
||||||
|
@ -43,26 +50,59 @@ func execAction(context *cli.Context) {
|
||||||
|
|
||||||
var exitCode int
|
var exitCode int
|
||||||
|
|
||||||
container, err := loadConfig()
|
process := &libcontainer.ProcessConfig{
|
||||||
|
Args: context.Args(),
|
||||||
|
Env: context.StringSlice("env"),
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
factory, err := libcontainer.New(context.GlobalString("root"), []string{os.Args[0], "init", "--fd", "3", "--"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := libcontainer.GetState(dataPath)
|
id := fmt.Sprintf("%x", md5.Sum([]byte(dataPath)))
|
||||||
|
container, err := factory.Load(id)
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
log.Fatalf("unable to read state.json: %s", err)
|
var config *libcontainer.Config
|
||||||
}
|
|
||||||
|
config, err = loadConfig()
|
||||||
if state != nil {
|
if err != nil {
|
||||||
exitCode, err = startInExistingContainer(container, state, context.String("func"), context)
|
log.Fatal(err)
|
||||||
} else {
|
}
|
||||||
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
|
container, err = factory.Create(id, config)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pid, err := container.StartProcess(process)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to exec: %s", err)
|
log.Fatalf("failed to exec: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p, err := os.FindProcess(pid)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to find the %d process: %s", pid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := p.Wait()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to wait the %d process: %s", pid, err)
|
||||||
|
}
|
||||||
|
container.Destroy()
|
||||||
|
|
||||||
|
status := ps.Sys().(syscall.WaitStatus)
|
||||||
|
if status.Exited() {
|
||||||
|
exitCode = status.ExitStatus()
|
||||||
|
} else if status.Signaled() {
|
||||||
|
exitCode = -int(status.Signal())
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Unexpected status")
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,44 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/docker/libcontainer/system"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/libcontainer/namespaces"
|
"github.com/docker/libcontainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dataPath = os.Getenv("data_path")
|
|
||||||
console = os.Getenv("console")
|
|
||||||
rawPipeFd = os.Getenv("pipe")
|
|
||||||
|
|
||||||
initCommand = cli.Command{
|
initCommand = cli.Command{
|
||||||
Name: "init",
|
Name: "init",
|
||||||
Usage: "runs the init process inside the namespace",
|
Usage: "runs the init process inside the namespace",
|
||||||
Action: initAction,
|
Action: initAction,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{"fd", 0, "internal pipe fd"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func initAction(context *cli.Context) {
|
func initAction(context *cli.Context) {
|
||||||
runtime.LockOSThread()
|
factory, err := libcontainer.New("", []string{})
|
||||||
|
|
||||||
container, err := loadConfig()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootfs, err := os.Getwd()
|
if context.Int("fd") == 0 {
|
||||||
if err != nil {
|
log.Fatal("--fd must be specified for init process")
|
||||||
|
}
|
||||||
|
|
||||||
|
fd := uintptr(context.Int("fd"))
|
||||||
|
|
||||||
|
if err := factory.StartInitialization(fd); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeFd, err := strconv.Atoi(rawPipeFd)
|
args := []string(context.Args())
|
||||||
if err != nil {
|
|
||||||
|
if err := system.Execv(args[0], args[0:], os.Environ()); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pipe := os.NewFile(uintptr(pipeFd), "pipe")
|
|
||||||
if err := namespaces.Init(container, rootfs, console, pipe, []string(context.Args())); err != nil {
|
|
||||||
log.Fatalf("unable to initialize for container: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ var statsCommand = cli.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func statsAction(context *cli.Context) {
|
func statsAction(context *cli.Context) {
|
||||||
factory, err := libcontainer.New(context.GlobalString("root"))
|
factory, err := libcontainer.New(context.GlobalString("root"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue