Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)

direct-test:
go get github.com/golang/glog && \
go test $(TEST_TAGS) -cover -v $(GO_PACKAGES)

direct-test-short:
Expand Down
3 changes: 3 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
// Container errors
ContainerNotExists
ContainerPaused
ContainerNotStopped

// Common errors
ConfigInvalid
Expand All @@ -34,6 +35,8 @@ func (c ErrorCode) String() string {
return "System error"
case ContainerNotExists:
return "Container does not exist"
case ContainerNotStopped:
return "Container isn't stopped"
default:
return "Unknown error"
}
Expand Down
10 changes: 10 additions & 0 deletions factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,14 @@ type Factory interface {
// Container is stopped
// System 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
}
96 changes: 91 additions & 5 deletions linux_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
package libcontainer

import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"

"github.com/docker/libcontainer/network"
"github.com/golang/glog"
)
Expand All @@ -13,6 +20,7 @@ type linuxContainer struct {
config *Config
state *State
cgroupManager CgroupManager
initArgs []string
}

func (c *linuxContainer) ID() string {
Expand All @@ -24,7 +32,7 @@ func (c *linuxContainer) Config() *Config {
}

func (c *linuxContainer) RunState() (RunState, error) {
panic("not implemented")
return Destroyed, nil // FIXME return a real state
}

func (c *linuxContainer) Processes() ([]int, error) {
Expand Down Expand Up @@ -53,13 +61,91 @@ func (c *linuxContainer) Stats() (*ContainerStats, error) {
}

func (c *linuxContainer) StartProcess(config *ProcessConfig) (int, error) {
glog.Info("start new container process")
panic("not implemented")
state, err := c.RunState()
if err != nil {
return -1, err
}

if state != Destroyed {
glog.Info("start new container process")
panic("not implemented")
}

if err := c.startInitProcess(config); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should switch depending on whether the init exists or not. This is a good place to start. Not at all something to do now :) but maybe worth a TODO

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the previous if statement does this. If a container isn't "Destroyed", a new processes will be executed in the existing container. Or do you mean something else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaaah yes I see, ignore me.

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 {
glog.Info("destroy container")
panic("not implemented")
state, err := c.RunState()
if err != nil {
return err
}

if state != Destroyed {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably fine for now, but eventually I do believe we want to be destructive by default.

return newGenericError(nil, ContainerNotStopped)
}

os.RemoveAll(c.root)
return nil
}

func (c *linuxContainer) Pause() error {
Expand Down
61 changes: 55 additions & 6 deletions linux_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,30 @@ var (
)

// New returns a linux based container factory based in the root directory.
func New(root string) (Factory, error) {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, newGenericError(err, SystemError)
func New(root string, initArgs []string) (Factory, error) {
if root != "" {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, newGenericError(err, SystemError)
}
}

return &linuxFactory{
root: root,
root: root,
initArgs: initArgs,
}, nil
}

// linuxFactory implements the default factory interface for linux based systems.
type linuxFactory struct {
// root is the root directory
root string
root string
initArgs []string
}

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) {
return nil, newGenericError(fmt.Errorf("Invalid id format: %v", id), InvalidIdFormat)
}
Expand All @@ -56,10 +63,43 @@ func (l *linuxFactory) Create(id string, config *Config) (Container, error) {
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) {
if l.root == "" {
return nil, newGenericError(fmt.Errorf("invalid root"), ConfigInvalid)
}
containerRoot := filepath.Join(l.root, id)
glog.Infof("loading container config from %s", containerRoot)
config, err := l.loadContainerConfig(containerRoot)
Expand All @@ -81,6 +121,7 @@ func (l *linuxFactory) Load(id string) (Container, error) {
config: config,
state: state,
cgroupManager: cgroupManager,
initArgs: l.initArgs,
}, nil
}

Expand Down Expand Up @@ -117,3 +158,11 @@ func (l *linuxFactory) loadContainerState(root string) (*State, error) {
}
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
}
6 changes: 3 additions & 3 deletions linux_factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestFactoryNew(t *testing.T) {
}
defer os.RemoveAll(root)

factory, err := New(root)
factory, err := New(root, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -54,7 +54,7 @@ func TestFactoryLoadNotExists(t *testing.T) {
}
defer os.RemoveAll(root)

factory, err := New(root)
factory, err := New(root, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -101,7 +101,7 @@ func TestFactoryLoadContainer(t *testing.T) {
t.Fatal(err)
}

factory, err := New(root)
factory, err := New(root, nil)
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 2 additions & 0 deletions nsinit/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
all:
go build -o nsinit .
56 changes: 48 additions & 8 deletions nsinit/exec.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"crypto/md5"
"fmt"
"io"
"log"
Expand All @@ -17,6 +18,12 @@ import (
"github.com/docker/libcontainer/namespaces"
)

var (
dataPath = os.Getenv("data_path")
console = os.Getenv("console")
rawPipeFd = os.Getenv("pipe")
)

var execCommand = cli.Command{
Name: "exec",
Usage: "execute a new command inside a container",
Expand All @@ -43,26 +50,59 @@ func execAction(context *cli.Context) {

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 {
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) {
log.Fatalf("unable to read state.json: %s", err)
}
var config *libcontainer.Config

if state != nil {
exitCode, err = startInExistingContainer(container, state, context.String("func"), context)
} else {
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
config, err = loadConfig()
if err != nil {
log.Fatal(err)
}
container, err = factory.Create(id, config)
}
if err != nil {
log.Fatal(err)
}

pid, err := container.StartProcess(process)
if err != nil {
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)
}

Expand Down
Loading