2013-01-19 00:13:39 +00:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2013-03-21 07:25:00 +00:00
|
|
|
"fmt"
|
2013-03-26 15:31:26 +00:00
|
|
|
"github.com/dotcloud/docker/rcli"
|
2013-01-29 21:50:27 +00:00
|
|
|
"github.com/kr/pty"
|
2013-01-19 00:13:39 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2013-01-22 02:03:23 +00:00
|
|
|
"log"
|
2013-01-19 00:13:39 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path"
|
2013-02-28 19:51:14 +00:00
|
|
|
"strconv"
|
2013-01-19 00:13:39 +00:00
|
|
|
"syscall"
|
2013-01-22 02:03:23 +00:00
|
|
|
"time"
|
2013-01-19 00:13:39 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Container struct {
|
2013-03-21 07:25:00 +00:00
|
|
|
root string
|
|
|
|
|
|
|
|
Id string
|
2013-01-22 19:13:22 +00:00
|
|
|
|
|
|
|
Created time.Time
|
|
|
|
|
2013-01-19 00:13:39 +00:00
|
|
|
Path string
|
|
|
|
Args []string
|
|
|
|
|
2013-03-21 07:25:00 +00:00
|
|
|
Config *Config
|
|
|
|
State State
|
|
|
|
Image string
|
2013-01-19 00:13:39 +00:00
|
|
|
|
2013-02-28 19:51:14 +00:00
|
|
|
network *NetworkInterface
|
|
|
|
NetworkSettings *NetworkSettings
|
2013-01-19 00:13:39 +00:00
|
|
|
|
2013-03-21 07:25:00 +00:00
|
|
|
SysInitPath string
|
|
|
|
cmd *exec.Cmd
|
|
|
|
stdout *writeBroadcaster
|
|
|
|
stderr *writeBroadcaster
|
|
|
|
stdin io.ReadCloser
|
|
|
|
stdinPipe io.WriteCloser
|
2013-01-26 23:56:42 +00:00
|
|
|
|
2013-03-30 16:05:53 +00:00
|
|
|
ptyStdinMaster io.Closer
|
|
|
|
ptyStdoutMaster io.Closer
|
|
|
|
ptyStderrMaster io.Closer
|
|
|
|
|
2013-03-29 15:41:48 +00:00
|
|
|
runtime *Runtime
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Config struct {
|
2013-04-03 01:07:16 +00:00
|
|
|
Hostname string
|
|
|
|
User string
|
|
|
|
Memory int64 // Memory limit (in bytes)
|
|
|
|
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
|
|
|
AttachStdin bool
|
|
|
|
AttachStdout bool
|
|
|
|
AttachStderr bool
|
2013-04-05 05:58:01 +00:00
|
|
|
PortSpecs []string
|
2013-04-03 01:07:16 +00:00
|
|
|
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
|
|
|
OpenStdin bool // Open stdin
|
|
|
|
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
|
|
|
Env []string
|
|
|
|
Cmd []string
|
|
|
|
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
2013-03-23 19:16:58 +00:00
|
|
|
}
|
|
|
|
|
2013-03-26 15:31:26 +00:00
|
|
|
func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
|
|
|
cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
|
|
|
|
if len(args) > 0 && args[0] != "--help" {
|
|
|
|
cmd.SetOutput(ioutil.Discard)
|
|
|
|
}
|
|
|
|
|
2013-04-01 20:03:24 +00:00
|
|
|
flHostname := cmd.String("h", "", "Container host name")
|
2013-03-29 00:12:23 +00:00
|
|
|
flUser := cmd.String("u", "", "Username or UID")
|
|
|
|
flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background")
|
2013-04-03 01:07:16 +00:00
|
|
|
flAttach := NewAttachOpts()
|
|
|
|
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
2013-03-29 00:12:23 +00:00
|
|
|
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
|
|
|
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
|
|
|
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
|
|
|
|
|
2013-04-05 05:58:01 +00:00
|
|
|
var flPorts ListOpts
|
2013-04-03 13:37:56 +00:00
|
|
|
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
|
2013-03-28 05:50:33 +00:00
|
|
|
|
2013-03-29 00:12:23 +00:00
|
|
|
var flEnv ListOpts
|
|
|
|
cmd.Var(&flEnv, "e", "Set environment variables")
|
2013-03-28 05:50:33 +00:00
|
|
|
|
2013-03-23 19:16:58 +00:00
|
|
|
if err := cmd.Parse(args); err != nil {
|
2013-03-23 19:39:09 +00:00
|
|
|
return nil, err
|
2013-03-23 19:16:58 +00:00
|
|
|
}
|
2013-04-03 14:06:35 +00:00
|
|
|
if *flDetach && len(flAttach) > 0 {
|
2013-04-03 01:07:16 +00:00
|
|
|
return nil, fmt.Errorf("Conflicting options: -a and -d")
|
|
|
|
}
|
|
|
|
// If neither -d or -a are set, attach to everything by default
|
2013-04-03 14:06:35 +00:00
|
|
|
if len(flAttach) == 0 && !*flDetach {
|
2013-04-03 01:07:16 +00:00
|
|
|
if !*flDetach {
|
|
|
|
flAttach.Set("stdout")
|
|
|
|
flAttach.Set("stderr")
|
|
|
|
if *flStdin {
|
|
|
|
flAttach.Set("stdin")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-03-25 14:17:11 +00:00
|
|
|
parsedArgs := cmd.Args()
|
|
|
|
runCmd := []string{}
|
|
|
|
image := ""
|
|
|
|
if len(parsedArgs) >= 1 {
|
|
|
|
image = cmd.Arg(0)
|
|
|
|
}
|
|
|
|
if len(parsedArgs) > 1 {
|
|
|
|
runCmd = parsedArgs[1:]
|
|
|
|
}
|
2013-03-23 19:16:58 +00:00
|
|
|
config := &Config{
|
2013-04-03 03:46:32 +00:00
|
|
|
Hostname: *flHostname,
|
2013-04-05 05:58:01 +00:00
|
|
|
PortSpecs: flPorts,
|
2013-04-03 01:07:16 +00:00
|
|
|
User: *flUser,
|
|
|
|
Tty: *flTty,
|
|
|
|
OpenStdin: *flStdin,
|
|
|
|
Memory: *flMemory,
|
|
|
|
AttachStdin: flAttach.Get("stdin"),
|
|
|
|
AttachStdout: flAttach.Get("stdout"),
|
|
|
|
AttachStderr: flAttach.Get("stderr"),
|
|
|
|
Env: flEnv,
|
|
|
|
Cmd: runCmd,
|
|
|
|
Image: image,
|
2013-03-23 19:16:58 +00:00
|
|
|
}
|
2013-04-02 18:02:19 +00:00
|
|
|
// When allocating stdin in attached mode, close stdin at client disconnect
|
2013-04-03 01:07:16 +00:00
|
|
|
if config.OpenStdin && config.AttachStdin {
|
2013-04-02 18:02:19 +00:00
|
|
|
config.StdinOnce = true
|
2013-03-23 19:16:58 +00:00
|
|
|
}
|
2013-03-23 19:39:09 +00:00
|
|
|
return config, nil
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
|
|
|
|
2013-02-28 19:51:14 +00:00
|
|
|
type NetworkSettings struct {
|
2013-02-25 22:06:22 +00:00
|
|
|
IpAddress string
|
|
|
|
IpPrefixLen int
|
2013-02-28 19:51:14 +00:00
|
|
|
Gateway string
|
2013-04-03 22:05:03 +00:00
|
|
|
Bridge string
|
2013-02-28 19:51:14 +00:00
|
|
|
PortMapping map[string]string
|
2013-02-25 22:06:22 +00:00
|
|
|
}
|
|
|
|
|
2013-01-26 23:56:42 +00:00
|
|
|
func (container *Container) Cmd() *exec.Cmd {
|
|
|
|
return container.cmd
|
|
|
|
}
|
|
|
|
|
2013-01-29 11:24:31 +00:00
|
|
|
func (container *Container) When() time.Time {
|
|
|
|
return container.Created
|
|
|
|
}
|
|
|
|
|
2013-03-21 07:25:00 +00:00
|
|
|
func (container *Container) FromDisk() error {
|
|
|
|
data, err := ioutil.ReadFile(container.jsonPath())
|
2013-01-25 22:39:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-21 07:25:00 +00:00
|
|
|
// Load container settings
|
|
|
|
if err := json.Unmarshal(data, container); err != nil {
|
2013-01-25 22:39:02 +00:00
|
|
|
return err
|
|
|
|
}
|
2013-03-21 07:25:00 +00:00
|
|
|
return nil
|
2013-01-25 22:39:02 +00:00
|
|
|
}
|
|
|
|
|
2013-03-21 07:25:00 +00:00
|
|
|
func (container *Container) ToDisk() (err error) {
|
2013-01-22 19:13:22 +00:00
|
|
|
data, err := json.Marshal(container)
|
2013-01-19 00:13:39 +00:00
|
|
|
if err != nil {
|
2013-01-22 19:13:22 +00:00
|
|
|
return
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
2013-03-21 07:25:00 +00:00
|
|
|
return ioutil.WriteFile(container.jsonPath(), data, 0666)
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) generateLXCConfig() error {
|
2013-03-21 07:25:00 +00:00
|
|
|
fo, err := os.Create(container.lxcConfigPath())
|
2013-01-19 00:13:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fo.Close()
|
|
|
|
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-29 23:17:20 +00:00
|
|
|
func (container *Container) startPty() error {
|
2013-04-04 18:23:51 +00:00
|
|
|
|
2013-03-29 00:12:23 +00:00
|
|
|
stdoutMaster, stdoutSlave, err := pty.Open()
|
2013-01-29 23:17:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-30 16:05:53 +00:00
|
|
|
container.ptyStdoutMaster = stdoutMaster
|
2013-03-29 00:12:23 +00:00
|
|
|
container.cmd.Stdout = stdoutSlave
|
2013-04-04 18:23:51 +00:00
|
|
|
container.cmd.Stderr = stdoutSlave
|
2013-01-29 23:17:20 +00:00
|
|
|
|
|
|
|
// Copy the PTYs to our broadcasters
|
|
|
|
go func() {
|
2013-04-02 08:45:17 +00:00
|
|
|
defer container.stdout.CloseWriters()
|
2013-03-29 15:18:43 +00:00
|
|
|
Debugf("[startPty] Begin of stdout pipe")
|
2013-03-29 00:12:23 +00:00
|
|
|
io.Copy(container.stdout, stdoutMaster)
|
2013-03-29 15:18:43 +00:00
|
|
|
Debugf("[startPty] End of stdout pipe")
|
2013-01-29 23:17:20 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
// stdin
|
|
|
|
if container.Config.OpenStdin {
|
2013-04-04 18:23:51 +00:00
|
|
|
container.cmd.Stdin = stdoutSlave
|
2013-01-29 23:17:20 +00:00
|
|
|
// FIXME: The following appears to be broken.
|
|
|
|
// "cannot set terminal process group (-1): Inappropriate ioctl for device"
|
2013-04-04 18:24:22 +00:00
|
|
|
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
2013-01-29 23:17:20 +00:00
|
|
|
go func() {
|
|
|
|
defer container.stdin.Close()
|
2013-03-29 15:18:43 +00:00
|
|
|
Debugf("[startPty] Begin of stdin pipe")
|
2013-04-04 18:23:51 +00:00
|
|
|
io.Copy(stdoutMaster, container.stdin)
|
2013-03-29 15:18:43 +00:00
|
|
|
Debugf("[startPty] End of stdin pipe")
|
2013-01-29 23:17:20 +00:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
if err := container.cmd.Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-29 00:12:23 +00:00
|
|
|
stdoutSlave.Close()
|
2013-01-29 23:17:20 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) start() error {
|
|
|
|
container.cmd.Stdout = container.stdout
|
|
|
|
container.cmd.Stderr = container.stderr
|
|
|
|
if container.Config.OpenStdin {
|
|
|
|
stdin, err := container.cmd.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
go func() {
|
|
|
|
defer stdin.Close()
|
2013-03-29 15:18:43 +00:00
|
|
|
Debugf("Begin of stdin pipe [start]")
|
2013-01-29 23:17:20 +00:00
|
|
|
io.Copy(stdin, container.stdin)
|
2013-03-29 15:18:43 +00:00
|
|
|
Debugf("End of stdin pipe [start]")
|
2013-01-29 23:17:20 +00:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
return container.cmd.Start()
|
|
|
|
}
|
|
|
|
|
2013-04-02 19:18:20 +00:00
|
|
|
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
|
|
|
var cStdout, cStderr io.ReadCloser
|
|
|
|
|
2013-04-02 06:52:20 +00:00
|
|
|
var nJobs int
|
|
|
|
errors := make(chan error, 3)
|
|
|
|
if stdin != nil && container.Config.OpenStdin {
|
|
|
|
nJobs += 1
|
|
|
|
if cStdin, err := container.StdinPipe(); err != nil {
|
|
|
|
errors <- err
|
|
|
|
} else {
|
|
|
|
go func() {
|
|
|
|
Debugf("[start] attach stdin\n")
|
2013-04-02 19:19:01 +00:00
|
|
|
defer Debugf("[end] attach stdin\n")
|
|
|
|
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
|
|
|
if cStdout != nil {
|
|
|
|
defer cStdout.Close()
|
|
|
|
}
|
|
|
|
if cStderr != nil {
|
|
|
|
defer cStderr.Close()
|
|
|
|
}
|
2013-04-02 06:52:20 +00:00
|
|
|
if container.Config.StdinOnce {
|
|
|
|
defer cStdin.Close()
|
|
|
|
}
|
|
|
|
_, err := io.Copy(cStdin, stdin)
|
|
|
|
if err != nil {
|
2013-04-02 19:19:01 +00:00
|
|
|
Debugf("[error] attach stdin: %s\n", err)
|
2013-04-02 06:52:20 +00:00
|
|
|
}
|
2013-04-02 19:18:20 +00:00
|
|
|
// Discard error, expecting pipe error
|
|
|
|
errors <- nil
|
2013-04-02 06:52:20 +00:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if stdout != nil {
|
|
|
|
nJobs += 1
|
|
|
|
if p, err := container.StdoutPipe(); err != nil {
|
|
|
|
errors <- err
|
|
|
|
} else {
|
|
|
|
cStdout = p
|
|
|
|
go func() {
|
|
|
|
Debugf("[start] attach stdout\n")
|
|
|
|
defer Debugf("[end] attach stdout\n")
|
2013-04-02 19:18:20 +00:00
|
|
|
// If we are in StdinOnce mode, then close stdin
|
|
|
|
if container.Config.StdinOnce {
|
|
|
|
if stdin != nil {
|
|
|
|
defer stdin.Close()
|
|
|
|
}
|
|
|
|
if stdinCloser != nil {
|
|
|
|
defer stdinCloser.Close()
|
|
|
|
}
|
|
|
|
}
|
2013-04-02 06:52:20 +00:00
|
|
|
_, err := io.Copy(stdout, cStdout)
|
|
|
|
if err != nil {
|
|
|
|
Debugf("[error] attach stdout: %s\n", err)
|
|
|
|
}
|
|
|
|
errors <- err
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if stderr != nil {
|
|
|
|
nJobs += 1
|
|
|
|
if p, err := container.StderrPipe(); err != nil {
|
|
|
|
errors <- err
|
|
|
|
} else {
|
|
|
|
cStderr = p
|
|
|
|
go func() {
|
|
|
|
Debugf("[start] attach stderr\n")
|
|
|
|
defer Debugf("[end] attach stderr\n")
|
2013-04-02 19:18:20 +00:00
|
|
|
// If we are in StdinOnce mode, then close stdin
|
|
|
|
if container.Config.StdinOnce {
|
|
|
|
if stdin != nil {
|
|
|
|
defer stdin.Close()
|
|
|
|
}
|
|
|
|
if stdinCloser != nil {
|
|
|
|
defer stdinCloser.Close()
|
|
|
|
}
|
|
|
|
}
|
2013-04-02 06:52:20 +00:00
|
|
|
_, err := io.Copy(stderr, cStderr)
|
|
|
|
if err != nil {
|
|
|
|
Debugf("[error] attach stderr: %s\n", err)
|
|
|
|
}
|
|
|
|
errors <- err
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Go(func() error {
|
|
|
|
if cStdout != nil {
|
|
|
|
defer cStdout.Close()
|
|
|
|
}
|
|
|
|
if cStderr != nil {
|
|
|
|
defer cStderr.Close()
|
|
|
|
}
|
|
|
|
// FIXME: how do clean up the stdin goroutine without the unwanted side effect
|
|
|
|
// of closing the passed stdin? Add an intermediary io.Pipe?
|
|
|
|
for i := 0; i < nJobs; i += 1 {
|
|
|
|
Debugf("Waiting for job %d/%d\n", i+1, nJobs)
|
|
|
|
if err := <-errors; err != nil {
|
|
|
|
Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
Debugf("Job %d completed successfully\n", i+1)
|
|
|
|
}
|
|
|
|
Debugf("All jobs completed successfully\n")
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2013-01-19 00:13:39 +00:00
|
|
|
func (container *Container) Start() error {
|
2013-03-31 21:15:10 +00:00
|
|
|
if container.State.Running {
|
|
|
|
return fmt.Errorf("The container %s is already running.", container.Id)
|
|
|
|
}
|
2013-03-21 07:25:00 +00:00
|
|
|
if err := container.EnsureMounted(); err != nil {
|
2013-01-19 00:13:39 +00:00
|
|
|
return err
|
|
|
|
}
|
2013-02-25 22:06:22 +00:00
|
|
|
if err := container.allocateNetwork(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-02-14 02:14:46 +00:00
|
|
|
if err := container.generateLXCConfig(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-01-19 00:13:39 +00:00
|
|
|
params := []string{
|
2013-01-22 02:39:52 +00:00
|
|
|
"-n", container.Id,
|
2013-03-21 07:25:00 +00:00
|
|
|
"-f", container.lxcConfigPath(),
|
2013-01-19 00:13:39 +00:00
|
|
|
"--",
|
2013-02-13 22:01:44 +00:00
|
|
|
"/sbin/init",
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
2013-02-21 01:47:09 +00:00
|
|
|
|
|
|
|
// Networking
|
2013-02-25 22:06:22 +00:00
|
|
|
params = append(params, "-g", container.network.Gateway.String())
|
2013-02-21 01:47:09 +00:00
|
|
|
|
|
|
|
// User
|
2013-02-14 01:24:35 +00:00
|
|
|
if container.Config.User != "" {
|
|
|
|
params = append(params, "-u", container.Config.User)
|
|
|
|
}
|
2013-02-21 01:47:09 +00:00
|
|
|
|
|
|
|
// Program
|
2013-02-14 01:24:35 +00:00
|
|
|
params = append(params, "--", container.Path)
|
2013-01-19 00:13:39 +00:00
|
|
|
params = append(params, container.Args...)
|
2013-02-21 01:47:09 +00:00
|
|
|
|
2013-03-29 05:07:25 +00:00
|
|
|
container.cmd = exec.Command("lxc-start", params...)
|
2013-01-19 00:13:39 +00:00
|
|
|
|
2013-03-23 03:36:34 +00:00
|
|
|
// Setup environment
|
|
|
|
container.cmd.Env = append(
|
|
|
|
[]string{
|
|
|
|
"HOME=/",
|
|
|
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
|
|
},
|
|
|
|
container.Config.Env...,
|
|
|
|
)
|
|
|
|
|
2013-03-29 15:46:06 +00:00
|
|
|
// Setup logging of stdout and stderr to disk
|
|
|
|
if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2013-01-29 23:17:20 +00:00
|
|
|
var err error
|
2013-01-29 01:50:12 +00:00
|
|
|
if container.Config.Tty {
|
2013-03-26 14:01:59 +00:00
|
|
|
container.cmd.Env = append(
|
|
|
|
[]string{"TERM=xterm"},
|
|
|
|
container.cmd.Env...,
|
2013-03-26 01:43:25 +00:00
|
|
|
)
|
2013-01-29 23:17:20 +00:00
|
|
|
err = container.startPty()
|
2013-01-29 01:50:12 +00:00
|
|
|
} else {
|
2013-01-29 23:17:20 +00:00
|
|
|
err = container.start()
|
2013-01-29 01:50:12 +00:00
|
|
|
}
|
2013-01-29 23:17:20 +00:00
|
|
|
if err != nil {
|
2013-01-19 00:13:39 +00:00
|
|
|
return err
|
|
|
|
}
|
2013-03-21 07:25:00 +00:00
|
|
|
// FIXME: save state on disk *first*, then converge
|
|
|
|
// this way disk state is used as a journal, eg. we can restore after crash etc.
|
2013-01-19 00:13:39 +00:00
|
|
|
container.State.setRunning(container.cmd.Process.Pid)
|
2013-03-21 07:25:00 +00:00
|
|
|
container.ToDisk()
|
2013-01-19 00:13:39 +00:00
|
|
|
go container.monitor()
|
2013-01-23 01:30:09 +00:00
|
|
|
return nil
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) Run() error {
|
|
|
|
if err := container.Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
container.Wait()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) Output() (output []byte, err error) {
|
|
|
|
pipe, err := container.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer pipe.Close()
|
|
|
|
if err := container.Start(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
output, err = ioutil.ReadAll(pipe)
|
|
|
|
container.Wait()
|
|
|
|
return output, err
|
|
|
|
}
|
|
|
|
|
2013-01-29 01:50:12 +00:00
|
|
|
// StdinPipe() returns a pipe connected to the standard input of the container's
|
|
|
|
// active process.
|
|
|
|
//
|
|
|
|
func (container *Container) StdinPipe() (io.WriteCloser, error) {
|
|
|
|
return container.stdinPipe, nil
|
|
|
|
}
|
|
|
|
|
2013-01-19 00:13:39 +00:00
|
|
|
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
|
|
|
|
reader, writer := io.Pipe()
|
|
|
|
container.stdout.AddWriter(writer)
|
|
|
|
return newBufReader(reader), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) StderrPipe() (io.ReadCloser, error) {
|
|
|
|
reader, writer := io.Pipe()
|
|
|
|
container.stderr.AddWriter(writer)
|
|
|
|
return newBufReader(reader), nil
|
|
|
|
}
|
|
|
|
|
2013-02-25 22:06:22 +00:00
|
|
|
func (container *Container) allocateNetwork() error {
|
2013-03-21 08:43:03 +00:00
|
|
|
iface, err := container.runtime.networkManager.Allocate()
|
2013-02-25 22:06:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-02-28 19:51:14 +00:00
|
|
|
container.NetworkSettings.PortMapping = make(map[string]string)
|
2013-04-05 05:58:01 +00:00
|
|
|
for _, spec := range container.Config.PortSpecs {
|
|
|
|
if nat, err := iface.AllocatePort(spec); err != nil {
|
2013-02-28 19:51:14 +00:00
|
|
|
iface.Release()
|
|
|
|
return err
|
|
|
|
} else {
|
2013-04-05 05:58:01 +00:00
|
|
|
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
|
2013-02-28 19:51:14 +00:00
|
|
|
}
|
|
|
|
}
|
2013-02-25 22:06:22 +00:00
|
|
|
container.network = iface
|
2013-04-03 22:05:03 +00:00
|
|
|
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
|
2013-02-28 19:51:14 +00:00
|
|
|
container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
|
|
|
|
container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
|
|
|
|
container.NetworkSettings.Gateway = iface.Gateway.String()
|
2013-02-25 22:06:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-04-01 05:04:59 +00:00
|
|
|
func (container *Container) releaseNetwork() {
|
2013-03-30 22:32:10 +00:00
|
|
|
container.network.Release()
|
2013-02-25 22:06:22 +00:00
|
|
|
container.network = nil
|
2013-02-28 19:51:14 +00:00
|
|
|
container.NetworkSettings = &NetworkSettings{}
|
2013-01-26 23:56:42 +00:00
|
|
|
}
|
|
|
|
|
2013-01-19 00:13:39 +00:00
|
|
|
func (container *Container) monitor() {
|
2013-01-22 23:03:40 +00:00
|
|
|
// Wait for the program to exit
|
2013-03-29 15:18:43 +00:00
|
|
|
Debugf("Waiting for process")
|
2013-03-29 15:29:59 +00:00
|
|
|
if err := container.cmd.Wait(); err != nil {
|
|
|
|
// Discard the error as any signals or non 0 returns will generate an error
|
|
|
|
Debugf("%s: Process: %s", container.Id, err)
|
|
|
|
}
|
2013-03-29 15:18:43 +00:00
|
|
|
Debugf("Process finished")
|
|
|
|
|
2013-01-23 01:30:09 +00:00
|
|
|
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
2013-01-22 02:03:23 +00:00
|
|
|
|
2013-01-22 23:03:40 +00:00
|
|
|
// Cleanup
|
2013-03-30 22:32:10 +00:00
|
|
|
container.releaseNetwork()
|
2013-03-30 16:08:53 +00:00
|
|
|
if container.Config.OpenStdin {
|
|
|
|
if err := container.stdin.Close(); err != nil {
|
|
|
|
Debugf("%s: Error close stdin: %s", container.Id, err)
|
|
|
|
}
|
|
|
|
}
|
2013-04-02 08:45:17 +00:00
|
|
|
if err := container.stdout.CloseWriters(); err != nil {
|
2013-03-29 15:29:59 +00:00
|
|
|
Debugf("%s: Error close stdout: %s", container.Id, err)
|
|
|
|
}
|
2013-04-02 08:45:17 +00:00
|
|
|
if err := container.stderr.CloseWriters(); err != nil {
|
2013-03-29 15:29:59 +00:00
|
|
|
Debugf("%s: Error close stderr: %s", container.Id, err)
|
|
|
|
}
|
2013-03-30 16:05:53 +00:00
|
|
|
|
|
|
|
if container.ptyStdinMaster != nil {
|
|
|
|
if err := container.ptyStdinMaster.Close(); err != nil {
|
|
|
|
Debugf("%s: Error close pty stdin master: %s", container.Id, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if container.ptyStdoutMaster != nil {
|
|
|
|
if err := container.ptyStdoutMaster.Close(); err != nil {
|
|
|
|
Debugf("%s: Error close pty stdout master: %s", container.Id, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if container.ptyStderrMaster != nil {
|
|
|
|
if err := container.ptyStderrMaster.Close(); err != nil {
|
|
|
|
Debugf("%s: Error close pty stderr master: %s", container.Id, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-21 07:25:00 +00:00
|
|
|
if err := container.Unmount(); err != nil {
|
2013-01-22 02:39:52 +00:00
|
|
|
log.Printf("%v: Failed to umount filesystem: %v", container.Id, err)
|
2013-01-22 02:03:23 +00:00
|
|
|
}
|
|
|
|
|
2013-02-14 03:05:57 +00:00
|
|
|
// Re-create a brand new stdin pipe once the container exited
|
|
|
|
if container.Config.OpenStdin {
|
|
|
|
container.stdin, container.stdinPipe = io.Pipe()
|
|
|
|
}
|
|
|
|
|
2013-01-22 02:03:23 +00:00
|
|
|
// Report status back
|
|
|
|
container.State.setStopped(exitCode)
|
2013-03-29 15:29:59 +00:00
|
|
|
if err := container.ToDisk(); err != nil {
|
2013-04-01 05:05:14 +00:00
|
|
|
// FIXME: there is a race condition here which causes this to fail during the unit tests.
|
|
|
|
// If another goroutine was waiting for Wait() to return before removing the container's root
|
|
|
|
// from the filesystem... At this point it may already have done so.
|
|
|
|
// This is because State.setStopped() has already been called, and has caused Wait()
|
|
|
|
// to return.
|
|
|
|
// FIXME: why are we serializing running state to disk in the first place?
|
|
|
|
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.Id, err)
|
2013-03-29 15:29:59 +00:00
|
|
|
}
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
|
|
|
|
2013-01-22 02:03:23 +00:00
|
|
|
func (container *Container) kill() error {
|
2013-03-24 02:51:35 +00:00
|
|
|
if container.cmd == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2013-01-25 23:36:47 +00:00
|
|
|
if err := container.cmd.Process.Kill(); err != nil {
|
2013-01-22 02:03:23 +00:00
|
|
|
return err
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
2013-01-22 02:03:23 +00:00
|
|
|
// Wait for the container to be actually stopped
|
2013-01-23 01:30:09 +00:00
|
|
|
container.Wait()
|
2013-01-22 02:03:23 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) Kill() error {
|
|
|
|
if !container.State.Running {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return container.kill()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) Stop() error {
|
|
|
|
if !container.State.Running {
|
2013-01-19 00:13:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
2013-01-22 02:03:23 +00:00
|
|
|
|
|
|
|
// 1. Send a SIGTERM
|
2013-03-29 05:07:25 +00:00
|
|
|
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
|
2013-03-30 07:23:12 +00:00
|
|
|
log.Print(string(output))
|
|
|
|
log.Print("Failed to send SIGTERM to the process, force killing")
|
2013-01-25 23:36:47 +00:00
|
|
|
if err := container.Kill(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-01-22 02:03:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Wait for the process to exit on its own
|
|
|
|
if err := container.WaitTimeout(10 * time.Second); err != nil {
|
2013-01-25 23:36:47 +00:00
|
|
|
log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
|
|
|
|
if err := container.Kill(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-01-22 02:03:23 +00:00
|
|
|
}
|
2013-01-19 00:13:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-22 23:03:40 +00:00
|
|
|
func (container *Container) Restart() error {
|
|
|
|
if err := container.Stop(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := container.Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-02-26 19:43:54 +00:00
|
|
|
// Wait blocks until the container stops running, then returns its exit code.
|
|
|
|
func (container *Container) Wait() int {
|
2013-01-25 23:36:47 +00:00
|
|
|
|
2013-01-19 00:13:39 +00:00
|
|
|
for container.State.Running {
|
|
|
|
container.State.wait()
|
|
|
|
}
|
2013-02-26 19:43:54 +00:00
|
|
|
return container.State.ExitCode
|
2013-01-19 00:13:39 +00:00
|
|
|
}
|
2013-01-22 02:03:23 +00:00
|
|
|
|
2013-03-22 00:47:23 +00:00
|
|
|
func (container *Container) ExportRw() (Archive, error) {
|
|
|
|
return Tar(container.rwPath(), Uncompressed)
|
2013-03-21 07:25:00 +00:00
|
|
|
}
|
|
|
|
|
2013-03-22 00:47:23 +00:00
|
|
|
func (container *Container) Export() (Archive, error) {
|
2013-03-21 07:25:00 +00:00
|
|
|
if err := container.EnsureMounted(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-03-22 00:47:23 +00:00
|
|
|
return Tar(container.RootfsPath(), Uncompressed)
|
2013-03-21 07:25:00 +00:00
|
|
|
}
|
|
|
|
|
2013-01-22 02:03:23 +00:00
|
|
|
func (container *Container) WaitTimeout(timeout time.Duration) error {
|
|
|
|
done := make(chan bool)
|
|
|
|
go func() {
|
|
|
|
container.Wait()
|
|
|
|
done <- true
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-time.After(timeout):
|
2013-03-29 15:19:42 +00:00
|
|
|
return fmt.Errorf("Timed Out")
|
2013-01-22 02:03:23 +00:00
|
|
|
case <-done:
|
|
|
|
return nil
|
|
|
|
}
|
2013-04-03 09:18:23 +00:00
|
|
|
panic("unreachable")
|
2013-01-22 02:03:23 +00:00
|
|
|
}
|
2013-03-21 07:25:00 +00:00
|
|
|
|
|
|
|
func (container *Container) EnsureMounted() error {
|
|
|
|
if mounted, err := container.Mounted(); err != nil {
|
|
|
|
return err
|
|
|
|
} else if mounted {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return container.Mount()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) Mount() error {
|
|
|
|
image, err := container.GetImage()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return image.Mount(container.RootfsPath(), container.rwPath())
|
|
|
|
}
|
|
|
|
|
2013-03-22 00:47:23 +00:00
|
|
|
func (container *Container) Changes() ([]Change, error) {
|
2013-03-21 07:25:00 +00:00
|
|
|
image, err := container.GetImage()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return image.Changes(container.rwPath())
|
|
|
|
}
|
|
|
|
|
2013-03-22 00:47:23 +00:00
|
|
|
func (container *Container) GetImage() (*Image, error) {
|
2013-03-21 07:25:00 +00:00
|
|
|
if container.runtime == nil {
|
|
|
|
return nil, fmt.Errorf("Can't get image of unregistered container")
|
|
|
|
}
|
|
|
|
return container.runtime.graph.Get(container.Image)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) Mounted() (bool, error) {
|
2013-03-22 00:47:23 +00:00
|
|
|
return Mounted(container.RootfsPath())
|
2013-03-21 07:25:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) Unmount() error {
|
2013-03-22 00:47:23 +00:00
|
|
|
return Unmount(container.RootfsPath())
|
2013-03-21 07:25:00 +00:00
|
|
|
}
|
|
|
|
|
2013-03-31 09:02:01 +00:00
|
|
|
// ShortId returns a shorthand version of the container's id for convenience.
|
|
|
|
// A collision with other container shorthands is very unlikely, but possible.
|
|
|
|
// In case of a collision a lookup with Runtime.Get() will fail, and the caller
|
|
|
|
// will need to use a langer prefix, or the full-length container Id.
|
|
|
|
func (container *Container) ShortId() string {
|
2013-04-01 05:11:55 +00:00
|
|
|
return TruncateId(container.Id)
|
2013-03-31 09:02:01 +00:00
|
|
|
}
|
|
|
|
|
2013-03-21 07:25:00 +00:00
|
|
|
func (container *Container) logPath(name string) string {
|
|
|
|
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.Id, name))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) ReadLog(name string) (io.Reader, error) {
|
|
|
|
return os.Open(container.logPath(name))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) jsonPath() string {
|
|
|
|
return path.Join(container.root, "config.json")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) lxcConfigPath() string {
|
|
|
|
return path.Join(container.root, "config.lxc")
|
|
|
|
}
|
|
|
|
|
|
|
|
// This method must be exported to be used from the lxc template
|
|
|
|
func (container *Container) RootfsPath() string {
|
|
|
|
return path.Join(container.root, "rootfs")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (container *Container) rwPath() string {
|
|
|
|
return path.Join(container.root, "rw")
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateId(id string) error {
|
|
|
|
if id == "" {
|
|
|
|
return fmt.Errorf("Invalid empty id")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|