Merge pull request #116 from jhowardmsft/runtimecompiles

runtime compiles on Windows
This commit is contained in:
Michael Crosby 2016-02-26 14:34:17 -08:00
commit cc00a35bfc
16 changed files with 390 additions and 281 deletions

View file

@ -78,7 +78,7 @@ func (s *apiServer) Signal(ctx context.Context, r *types.SignalRequest) (*types.
}
func (s *apiServer) AddProcess(ctx context.Context, r *types.AddProcessRequest) (*types.AddProcessResponse, error) {
process := &specs.Process{
process := &runtime.ProcessSpec{
Terminal: r.Terminal,
Args: r.Args,
Env: r.Env,

View file

@ -4,14 +4,9 @@ import (
"encoding/json"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
"github.com/Sirupsen/logrus"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/specs"
)
type Container interface {
@ -22,7 +17,7 @@ type Container interface {
// Start starts the init process of the container
Start(checkpoint string, s Stdio) (Process, error)
// Exec starts another process in an existing container
Exec(string, specs.Process, Stdio) (Process, error)
Exec(string, ProcessSpec, Stdio) (Process, error)
// Delete removes the container's state and any resources
Delete() error
// Processes returns all the containers processes that have been added
@ -173,81 +168,8 @@ func (c *container) Labels() []string {
return c.labels
}
func (c *container) Start(checkpoint string, s Stdio) (Process, error) {
processRoot := filepath.Join(c.root, c.id, InitProcessID)
if err := os.Mkdir(processRoot, 0755); err != nil {
return nil, err
}
cmd := exec.Command("containerd-shim",
c.id, c.bundle,
)
cmd.Dir = processRoot
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
spec, err := c.readSpec()
if err != nil {
return nil, err
}
config := &processConfig{
checkpoint: checkpoint,
root: processRoot,
id: InitProcessID,
c: c,
stdio: s,
spec: spec,
processSpec: spec.Process,
}
p, err := newProcess(config)
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
if _, err := p.getPid(); err != nil {
return p, nil
}
c.processes[InitProcessID] = p
return p, nil
}
func (c *container) Exec(pid string, spec specs.Process, s Stdio) (Process, error) {
processRoot := filepath.Join(c.root, c.id, pid)
if err := os.Mkdir(processRoot, 0755); err != nil {
return nil, err
}
cmd := exec.Command("containerd-shim",
c.id, c.bundle,
)
cmd.Dir = processRoot
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
config := &processConfig{
exec: true,
id: pid,
root: processRoot,
c: c,
processSpec: spec,
stdio: s,
}
p, err := newProcess(config)
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
if _, err := p.getPid(); err != nil {
return p, nil
}
c.processes[pid] = p
return p, nil
}
func (c *container) readSpec() (*platformSpec, error) {
var spec platformSpec
func (c *container) readSpec() (*PlatformSpec, error) {
var spec PlatformSpec
f, err := os.Open(filepath.Join(c.bundle, "config.json"))
if err != nil {
return nil, err
@ -259,14 +181,6 @@ func (c *container) readSpec() (*platformSpec, error) {
return &spec, nil
}
func (c *container) Pause() error {
return exec.Command("runc", "pause", c.id).Run()
}
func (c *container) Resume() error {
return exec.Command("runc", "resume", c.id).Run()
}
func (c *container) State() State {
return Running
}
@ -287,113 +201,3 @@ func (c *container) RemoveProcess(pid string) error {
delete(c.processes, pid)
return os.RemoveAll(filepath.Join(c.root, c.id, pid))
}
func (c *container) Checkpoints() ([]Checkpoint, error) {
dirs, err := ioutil.ReadDir(filepath.Join(c.bundle, "checkpoints"))
if err != nil {
return nil, err
}
var out []Checkpoint
for _, d := range dirs {
if !d.IsDir() {
continue
}
path := filepath.Join(c.bundle, "checkpoints", d.Name(), "config.json")
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var cpt Checkpoint
if err := json.Unmarshal(data, &cpt); err != nil {
return nil, err
}
out = append(out, cpt)
}
return out, nil
}
func (c *container) Checkpoint(cpt Checkpoint) error {
if err := os.MkdirAll(filepath.Join(c.bundle, "checkpoints"), 0755); err != nil {
return err
}
path := filepath.Join(c.bundle, "checkpoints", cpt.Name)
if err := os.Mkdir(path, 0755); err != nil {
return err
}
f, err := os.Create(filepath.Join(path, "config.json"))
if err != nil {
return err
}
cpt.Created = time.Now()
err = json.NewEncoder(f).Encode(cpt)
f.Close()
if err != nil {
return err
}
args := []string{
"checkpoint",
"--image-path", path,
}
add := func(flags ...string) {
args = append(args, flags...)
}
if !cpt.Exit {
add("--leave-running")
}
if cpt.Shell {
add("--shell-job")
}
if cpt.Tcp {
add("--tcp-established")
}
if cpt.UnixSockets {
add("--ext-unix-sk")
}
add(c.id)
return exec.Command("runc", args...).Run()
}
func (c *container) DeleteCheckpoint(name string) error {
return os.RemoveAll(filepath.Join(c.bundle, "checkpoints", name))
}
func (c *container) Pids() ([]int, error) {
container, err := c.getLibctContainer()
if err != nil {
return nil, err
}
return container.Processes()
}
func (c *container) Stats() (*Stat, error) {
container, err := c.getLibctContainer()
if err != nil {
return nil, err
}
now := time.Now()
stats, err := container.Stats()
if err != nil {
return nil, err
}
return &Stat{
Timestamp: now,
Data: stats,
}, nil
}
func (c *container) getLibctContainer() (libcontainer.Container, error) {
f, err := libcontainer.New(specs.LinuxStateDirectory, libcontainer.Cgroupfs)
if err != nil {
return nil, err
}
return f.Load(c.id)
}
func hostIDFromMap(id uint32, mp []specs.IDMapping) int {
for _, m := range mp {
if (id >= m.ContainerID) && (id <= (m.ContainerID + m.Size - 1)) {
return int(m.HostID + (id - m.ContainerID))
}
}
return 0
}

View file

@ -1,8 +1,19 @@
package runtime
import "github.com/opencontainers/specs"
import (
"encoding/json"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
func getRootIDs(s *platformSpec) (int, int, error) {
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/specs"
)
func getRootIDs(s *PlatformSpec) (int, int, error) {
if s == nil {
return 0, 0, nil
}
@ -20,3 +31,194 @@ func getRootIDs(s *platformSpec) (int, int, error) {
gid := hostIDFromMap(0, s.Linux.GIDMappings)
return uid, gid, nil
}
func (c *container) Pause() error {
return exec.Command("runc", "pause", c.id).Run()
}
func (c *container) Resume() error {
return exec.Command("runc", "resume", c.id).Run()
}
func (c *container) Checkpoints() ([]Checkpoint, error) {
dirs, err := ioutil.ReadDir(filepath.Join(c.bundle, "checkpoints"))
if err != nil {
return nil, err
}
var out []Checkpoint
for _, d := range dirs {
if !d.IsDir() {
continue
}
path := filepath.Join(c.bundle, "checkpoints", d.Name(), "config.json")
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
var cpt Checkpoint
if err := json.Unmarshal(data, &cpt); err != nil {
return nil, err
}
out = append(out, cpt)
}
return out, nil
}
func (c *container) Checkpoint(cpt Checkpoint) error {
if err := os.MkdirAll(filepath.Join(c.bundle, "checkpoints"), 0755); err != nil {
return err
}
path := filepath.Join(c.bundle, "checkpoints", cpt.Name)
if err := os.Mkdir(path, 0755); err != nil {
return err
}
f, err := os.Create(filepath.Join(path, "config.json"))
if err != nil {
return err
}
cpt.Created = time.Now()
err = json.NewEncoder(f).Encode(cpt)
f.Close()
if err != nil {
return err
}
args := []string{
"checkpoint",
"--image-path", path,
}
add := func(flags ...string) {
args = append(args, flags...)
}
if !cpt.Exit {
add("--leave-running")
}
if cpt.Shell {
add("--shell-job")
}
if cpt.Tcp {
add("--tcp-established")
}
if cpt.UnixSockets {
add("--ext-unix-sk")
}
add(c.id)
return exec.Command("runc", args...).Run()
}
func (c *container) DeleteCheckpoint(name string) error {
return os.RemoveAll(filepath.Join(c.bundle, "checkpoints", name))
}
func (c *container) Start(checkpoint string, s Stdio) (Process, error) {
processRoot := filepath.Join(c.root, c.id, InitProcessID)
if err := os.Mkdir(processRoot, 0755); err != nil {
return nil, err
}
cmd := exec.Command("containerd-shim",
c.id, c.bundle,
)
cmd.Dir = processRoot
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
spec, err := c.readSpec()
if err != nil {
return nil, err
}
config := &processConfig{
checkpoint: checkpoint,
root: processRoot,
id: InitProcessID,
c: c,
stdio: s,
spec: spec,
processSpec: ProcessSpec(spec.Process),
}
p, err := newProcess(config)
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
if _, err := p.getPid(); err != nil {
return p, nil
}
c.processes[InitProcessID] = p
return p, nil
}
func (c *container) Exec(pid string, spec ProcessSpec, s Stdio) (Process, error) {
processRoot := filepath.Join(c.root, c.id, pid)
if err := os.Mkdir(processRoot, 0755); err != nil {
return nil, err
}
cmd := exec.Command("containerd-shim",
c.id, c.bundle,
)
cmd.Dir = processRoot
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
config := &processConfig{
exec: true,
id: pid,
root: processRoot,
c: c,
processSpec: spec,
stdio: s,
}
p, err := newProcess(config)
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
if _, err := p.getPid(); err != nil {
return p, nil
}
c.processes[pid] = p
return p, nil
}
func (c *container) getLibctContainer() (libcontainer.Container, error) {
f, err := libcontainer.New(specs.LinuxStateDirectory, libcontainer.Cgroupfs)
if err != nil {
return nil, err
}
return f.Load(c.id)
}
func hostIDFromMap(id uint32, mp []specs.IDMapping) int {
for _, m := range mp {
if (id >= m.ContainerID) && (id <= (m.ContainerID + m.Size - 1)) {
return int(m.HostID + (id - m.ContainerID))
}
}
return 0
}
func (c *container) Pids() ([]int, error) {
container, err := c.getLibctContainer()
if err != nil {
return nil, err
}
return container.Processes()
}
func (c *container) Stats() (*Stat, error) {
container, err := c.getLibctContainer()
if err != nil {
return nil, err
}
now := time.Now()
stats, err := container.Stats()
if err != nil {
return nil, err
}
return &Stat{
Timestamp: now,
Data: stats,
}, nil
}

View file

@ -1,5 +1,49 @@
package runtime
func getRootIDs(s *platformSpec) (int, int, error) {
import "errors"
func getRootIDs(s *PlatformSpec) (int, int, error) {
return 0, 0, nil
}
func (c *container) Pause() error {
return errors.New("Pause not supported on Windows")
}
func (c *container) Resume() error {
return errors.New("Resume not supported on Windows")
}
func (c *container) Checkpoints() ([]Checkpoint, error) {
return nil, errors.New("Checkpoints not supported on Windows ")
}
func (c *container) Checkpoint(cpt Checkpoint) error {
return errors.New("Checkpoint not supported on Windows ")
}
func (c *container) DeleteCheckpoint(name string) error {
return errors.New("DeleteCheckpoint not supported on Windows ")
}
// TODO Windows: Implement me.
// This will have a very different implementation on Windows.
func (c *container) Start(checkpoint string, s Stdio) (Process, error) {
return nil, errors.New("Start not yet implemented on Windows")
}
// TODO Windows: Implement me.
// This will have a very different implementation on Windows.
func (c *container) Exec(pid string, spec ProcessSpec, s Stdio) (Process, error) {
return nil, errors.New("Exec not yet implemented on Windows")
}
// TODO Windows: Implement me.
func (c *container) Pids() ([]int, error) {
return nil, errors.New("Pids not yet implemented on Windows")
}
// TODO Windows: Implement me. (Not yet supported by docker on Windows either...)
func (c *container) Stats() (*Stat, error) {
return nil, errors.New("Stats not yet implemented on Windows")
}

View file

@ -9,8 +9,6 @@ import (
"path/filepath"
"strconv"
"time"
"github.com/opencontainers/specs"
)
type Process interface {
@ -28,7 +26,7 @@ type Process interface {
// has not exited
ExitStatus() (int, error)
// Spec returns the process spec that created the process
Spec() specs.Process
Spec() ProcessSpec
// Signal sends the provided signal to the process
Signal(os.Signal) error
// Container returns the container that the process belongs to
@ -42,8 +40,8 @@ type Process interface {
type processConfig struct {
id string
root string
processSpec specs.Process
spec *platformSpec
processSpec ProcessSpec
spec *PlatformSpec
c *container
stdio Stdio
exec bool
@ -67,16 +65,9 @@ func newProcess(config *processConfig) (*process, error) {
return nil, err
}
defer f.Close()
if err := json.NewEncoder(f).Encode(ProcessState{
Process: config.processSpec,
Exec: config.exec,
Checkpoint: config.checkpoint,
RootUID: uid,
RootGID: gid,
Stdin: config.stdio.Stdin,
Stdout: config.stdio.Stdout,
Stderr: config.stdio.Stderr,
}); err != nil {
ps := populateProcessStateForEncoding(config, uid, gid)
if err := json.NewEncoder(f).Encode(ps); err != nil {
return nil, err
}
exit, err := getExitPipe(filepath.Join(config.root, ExitFile))
@ -97,7 +88,7 @@ func loadProcess(root, id string, c *container, s *ProcessState) (*process, erro
root: root,
id: id,
container: c,
spec: s.Process,
spec: s.ProcessSpec,
stdio: Stdio{
Stdin: s.Stdin,
Stdout: s.Stdout,
@ -128,7 +119,7 @@ type process struct {
exitPipe *os.File
controlPipe *os.File
container *container
spec specs.Process
spec ProcessSpec
stdio Stdio
}
@ -173,7 +164,7 @@ func (p *process) ExitStatus() (int, error) {
return strconv.Atoi(string(data))
}
func (p *process) Spec() specs.Process {
func (p *process) Spec() ProcessSpec {
return p.spec
}

View file

@ -25,3 +25,18 @@ func getControlPipe(path string) (*os.File, error) {
func (p *process) Signal(s os.Signal) error {
return syscall.Kill(p.pid, s.(syscall.Signal))
}
func populateProcessStateForEncoding(config *processConfig, uid int, gid int) ProcessState {
return ProcessState{
ProcessSpec: config.processSpec,
Exec: config.exec,
PlatformProcessState: PlatformProcessState{
Checkpoint: config.checkpoint,
RootUID: uid,
RootGID: gid,
},
Stdin: config.stdio.Stdin,
Stdout: config.stdio.Stdout,
Stderr: config.stdio.Stderr,
}
}

View file

@ -17,3 +17,13 @@ func getControlPipe(path string) (*os.File, error) {
func (p *process) Signal(s os.Signal) error {
return nil
}
func populateProcessStateForEncoding(config *processConfig, uid int, gid int) ProcessState {
return ProcessState{
ProcessSpec: config.processSpec,
Exec: config.exec,
Stdin: config.stdio.Stdin,
Stdout: config.stdio.Stdout,
Stderr: config.stdio.Stderr,
}
}

View file

@ -3,8 +3,6 @@ package runtime
import (
"errors"
"time"
"github.com/opencontainers/specs"
)
var (
@ -44,14 +42,13 @@ type state struct {
}
type ProcessState struct {
specs.Process
Exec bool `json:"exec"`
Checkpoint string `json:"checkpoint"`
RootUID int `json:"rootUID"`
RootGID int `json:"rootGID"`
Stdin string `json:"containerdStdin"`
Stdout string `json:"containerdStdout"`
Stderr string `json:"containerdStderr"`
ProcessSpec
Exec bool `json:"exec"`
Stdin string `json:"containerdStdin"`
Stdout string `json:"containerdStdout"`
Stderr string `json:"containerdStderr"`
PlatformProcessState
}
type Stat struct {
@ -63,18 +60,3 @@ type Stat struct {
// can return what they want and we could marshal to json or whatever.
Data interface{}
}
type Checkpoint struct {
// Timestamp is the time that checkpoint happened
Created time.Time `json:"created"`
// Name is the name of the checkpoint
Name string `json:"name"`
// Tcp checkpoints open tcp connections
Tcp bool `json:"tcp"`
// UnixSockets persists unix sockets in the checkpoint
UnixSockets bool `json:"unixSockets"`
// Shell persists tty sessions in the checkpoint
Shell bool `json:"shell"`
// Exit exits the container after the checkpoint is finished
Exit bool `json:"exit"`
}

25
runtime/runtime_linux.go Normal file
View file

@ -0,0 +1,25 @@
package runtime
import "time"
type Checkpoint struct {
// Timestamp is the time that checkpoint happened
Created time.Time `json:"created"`
// Name is the name of the checkpoint
Name string `json:"name"`
// Tcp checkpoints open tcp connections
Tcp bool `json:"tcp"`
// UnixSockets persists unix sockets in the checkpoint
UnixSockets bool `json:"unixSockets"`
// Shell persists tty sessions in the checkpoint
Shell bool `json:"shell"`
// Exit exits the container after the checkpoint is finished
Exit bool `json:"exit"`
}
// PlatformProcessState container platform-specific fields in the ProcessState structure
type PlatformProcessState struct {
Checkpoint string `json:"checkpoint"`
RootUID int `json:"rootUID"`
RootGID int `json:"rootGID"`
}

View file

@ -0,0 +1,10 @@
package runtime
// Checkpoint is not supported on Windows.
// TODO Windows: Can eventually be factored out entirely.
type Checkpoint struct {
}
// PlatformProcessState container platform-specific fields in the ProcessState structure
type PlatformProcessState struct {
}

View file

@ -2,4 +2,5 @@ package runtime
import "github.com/opencontainers/specs"
type platformSpec specs.LinuxSpec
type PlatformSpec specs.LinuxSpec
type ProcessSpec specs.Process

View file

@ -1,32 +1,8 @@
package runtime
type Spec struct {
// Version is the version of the specification that is supported.
Version string `json:"ociVersion"`
// Platform is the host information for OS and Arch.
// TEMPORARY HACK Platform Platform `json:"platform"`
// Process is the container's main process.
// TEMPORARY HACK Process Process `json:"process"`
// Root is the root information for the container's filesystem.
// TEMPORARY HACK Root Root `json:"root"`
// Hostname is the container's host name.
// TEMPORARY HACK Hostname string `json:"hostname,omitempty"`
// Mounts profile configuration for adding mounts to the container's filesystem.
// TEMPORARY HACK Mounts []Mount `json:"mounts"`
// Hooks are the commands run at various lifecycle events of the container.
// TEMPORARY HACK Hooks Hooks `json:"hooks"`
}
// Temporary Windows version of the spec in lieu of opencontainers/specs having
// Windows support currently.
import "github.com/docker/containerd/specs"
// TODO Windows - Interim hack. Needs implementing.
type WindowsSpec struct {
Spec
// Windows is platform specific configuration for Windows based containers.
Windows Windows `json:"windows"`
}
// Windows contains platform specific configuration for Windows based containers.
type Windows struct {
}
type platformSpec WindowsSpec
type PlatformSpec specs.WindowsSpec
type ProcessSpec specs.Process

3
specs/unsupported.go Normal file
View file

@ -0,0 +1,3 @@
// +build !windows
package specs

48
specs/windows.go Normal file
View file

@ -0,0 +1,48 @@
package specs
// This is a temporary module in lieu of opencontainers/specs being compatible
// currently on Windows.
// Process contains information to start a specific application inside the container.
type Process struct {
// Terminal creates an interactive terminal for the container.
Terminal bool `json:"terminal"`
// User specifies user information for the process.
// TEMPORARY HACK User User `json:"user"`
// Args specifies the binary and arguments for the application to execute.
Args []string `json:"args"`
// Env populates the process environment for the process.
Env []string `json:"env,omitempty"`
// Cwd is the current working directory for the process and must be
// relative to the container's root.
Cwd string `json:"cwd"`
}
type Spec struct {
// Version is the version of the specification that is supported.
Version string `json:"ociVersion"`
// Platform is the host information for OS and Arch.
// TEMPORARY HACK Platform Platform `json:"platform"`
// Process is the container's main process.
Process Process `json:"process"`
// Root is the root information for the container's filesystem.
// TEMPORARY HACK Root Root `json:"root"`
// Hostname is the container's host name.
// TEMPORARY HACK Hostname string `json:"hostname,omitempty"`
// Mounts profile configuration for adding mounts to the container's filesystem.
// TEMPORARY HACK Mounts []Mount `json:"mounts"`
// Hooks are the commands run at various lifecycle events of the container.
// TEMPORARY HACK Hooks Hooks `json:"hooks"`
}
// Windows contains platform specific configuration for Windows based containers.
type Windows struct {
}
// TODO Windows - Interim hack. Needs implementing.
type WindowsSpec struct {
Spec
// Windows is platform specific configuration for Windows based containers.
Windows Windows `json:"windows"`
}

View file

@ -4,7 +4,6 @@ import (
"time"
"github.com/docker/containerd/runtime"
"github.com/opencontainers/specs"
)
type AddProcessTask struct {
@ -14,7 +13,7 @@ type AddProcessTask struct {
Stdout string
Stderr string
Stdin string
ProcessSpec *specs.Process
ProcessSpec *runtime.ProcessSpec
StartResponse chan StartResponse
}

View file

@ -6,7 +6,6 @@ import (
"testing"
"github.com/docker/containerd/runtime"
"github.com/opencontainers/specs"
)
type testProcess struct {
@ -45,8 +44,8 @@ func (p *testProcess) Container() runtime.Container {
return nil
}
func (p *testProcess) Spec() specs.Process {
return specs.Process{}
func (p *testProcess) Spec() runtime.ProcessSpec {
return runtime.ProcessSpec{}
}
func (p *testProcess) Signal(os.Signal) error {