a27b4b8c |
package docker
import (
"encoding/json" |
cf19be44 |
"flag" |
7c57a4cf |
"fmt" |
88ef309a |
"github.com/dotcloud/docker/term" |
2e69e172 |
"github.com/dotcloud/docker/utils" |
94b1cf4b |
"github.com/kr/pty" |
a27b4b8c |
"io"
"io/ioutil" |
24dac228 |
"log" |
a27b4b8c |
"os"
"os/exec"
"path" |
a91b7109 |
"path/filepath" |
931ca464 |
"sort" |
09eacdfa |
"strconv" |
931ca464 |
"strings" |
a27b4b8c |
"syscall" |
24dac228 |
"time" |
a27b4b8c |
)
type Container struct { |
7c57a4cf |
root string
|
fd224ee5 |
ID string |
8e9bb02c |
Created time.Time
|
a27b4b8c |
Path string
Args []string
|
7c57a4cf |
Config *Config
State State
Image string |
a27b4b8c |
|
09eacdfa |
network *NetworkInterface
NetworkSettings *NetworkSettings |
a27b4b8c |
|
1f9f5eed |
SysInitPath string
ResolvConfPath string
cmd *exec.Cmd |
2e69e172 |
stdout *utils.WriteBroadcaster
stderr *utils.WriteBroadcaster |
1f9f5eed |
stdin io.ReadCloser
stdinPipe io.WriteCloser |
7d889554 |
ptyMaster io.Closer |
5252ab69 |
|
7a565a04 |
runtime *Runtime |
329f4449 |
waitLock chan struct{} |
6fb495bf |
Volumes map[string]string |
a27b4b8c |
}
type Config struct { |
c04af2a3 |
Hostname string
User string
Memory int64 // Memory limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap |
efd9becb |
CpuShares int64 // CPU shares (relative weight vs. other containers) |
c04af2a3 |
AttachStdin bool
AttachStdout bool
AttachStderr bool |
2aad4a34 |
PortSpecs []string |
c04af2a3 |
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 |
7673afc8 |
Dns []string |
c04af2a3 |
Image string // Name of the image as it was passed by the operator (eg. could be symbolic) |
faf8daa7 |
Volumes map[string]struct{} |
4099a313 |
VolumesFrom string |
031f91df |
}
|
10c0e990 |
func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) { |
b295239d |
cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") |
2333be46 |
if len(args) > 0 && args[0] != "--help" {
cmd.SetOutput(ioutil.Discard)
}
|
f65fc1e7 |
flHostname := cmd.String("h", "", "Container host name") |
a6da7f13 |
flUser := cmd.String("u", "", "Username or UID")
flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background") |
c04af2a3 |
flAttach := NewAttachOpts()
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.") |
a6da7f13 |
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)")
|
10c0e990 |
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") |
1967c834 |
*flMemory = 0
}
|
e36752e0 |
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") |
efd9becb |
|
2aad4a34 |
var flPorts ListOpts |
b74d1c92 |
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)") |
dcc9dfb2 |
|
a6da7f13 |
var flEnv ListOpts
cmd.Var(&flEnv, "e", "Set environment variables") |
dcc9dfb2 |
|
7673afc8 |
var flDns ListOpts
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
1df5f409 |
flVolumes := NewPathOpts()
cmd.Var(flVolumes, "v", "Attach a data volume")
|
4099a313 |
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
|
031f91df |
if err := cmd.Parse(args); err != nil { |
cf19be44 |
return nil, cmd, err |
031f91df |
} |
4f36039e |
if *flDetach && len(flAttach) > 0 { |
cf19be44 |
return nil, cmd, fmt.Errorf("Conflicting options: -a and -d") |
c04af2a3 |
}
// If neither -d or -a are set, attach to everything by default |
4f36039e |
if len(flAttach) == 0 && !*flDetach { |
c04af2a3 |
if !*flDetach {
flAttach.Set("stdout")
flAttach.Set("stderr")
if *flStdin {
flAttach.Set("stdin")
}
}
} |
c8ca50b4 |
parsedArgs := cmd.Args()
runCmd := []string{}
image := ""
if len(parsedArgs) >= 1 {
image = cmd.Arg(0)
}
if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:]
} |
031f91df |
config := &Config{ |
15c3096e |
Hostname: *flHostname, |
2aad4a34 |
PortSpecs: flPorts, |
c04af2a3 |
User: *flUser,
Tty: *flTty,
OpenStdin: *flStdin,
Memory: *flMemory, |
efd9becb |
CpuShares: *flCpuShares, |
c04af2a3 |
AttachStdin: flAttach.Get("stdin"),
AttachStdout: flAttach.Get("stdout"),
AttachStderr: flAttach.Get("stderr"),
Env: flEnv,
Cmd: runCmd, |
7673afc8 |
Dns: flDns, |
c04af2a3 |
Image: image, |
1df5f409 |
Volumes: flVolumes, |
4099a313 |
VolumesFrom: *flVolumesFrom, |
031f91df |
} |
640efc2e |
|
10c0e990 |
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") |
640efc2e |
config.MemorySwap = -1
}
|
aea2675f |
// When allocating stdin in attached mode, close stdin at client disconnect |
c04af2a3 |
if config.OpenStdin && config.AttachStdin { |
aea2675f |
config.StdinOnce = true |
031f91df |
} |
cf19be44 |
return config, cmd, nil |
a27b4b8c |
}
|
09eacdfa |
type NetworkSettings struct { |
fd224ee5 |
IPAddress string
IPPrefixLen int |
09eacdfa |
Gateway string |
d9a9bfc9 |
Bridge string |
09eacdfa |
PortMapping map[string]string |
c08f5b2b |
}
|
931ca464 |
// String returns a human-readable description of the port mapping defined in the settings
func (settings *NetworkSettings) PortMappingHuman() string {
var mapping []string
for private, public := range settings.PortMapping {
mapping = append(mapping, fmt.Sprintf("%s->%s", public, private))
}
sort.Strings(mapping)
return strings.Join(mapping, ", ")
}
|
97514831 |
// Inject the io.Reader at the given path. Note: do not close the reader
func (container *Container) Inject(file io.Reader, pth string) error { |
9db4972a |
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
return err
}
// FIXME: Handle permissions/already existing dest
dest, err := os.Create(path.Join(container.rwPath(), pth)) |
97514831 |
if err != nil {
return err
}
if _, err := io.Copy(dest, file); err != nil {
return err
}
return nil
}
|
5d6dd22f |
func (container *Container) Cmd() *exec.Cmd {
return container.cmd
}
|
c7a944ca |
func (container *Container) When() time.Time {
return container.Created
}
|
7c57a4cf |
func (container *Container) FromDisk() error {
data, err := ioutil.ReadFile(container.jsonPath()) |
11b65a00 |
if err != nil {
return err
} |
7c57a4cf |
// Load container settings
if err := json.Unmarshal(data, container); err != nil { |
11b65a00 |
return err
} |
7c57a4cf |
return nil |
11b65a00 |
}
|
7c57a4cf |
func (container *Container) ToDisk() (err error) { |
8e9bb02c |
data, err := json.Marshal(container) |
a27b4b8c |
if err != nil { |
8e9bb02c |
return |
a27b4b8c |
} |
7c57a4cf |
return ioutil.WriteFile(container.jsonPath(), data, 0666) |
a27b4b8c |
}
func (container *Container) generateLXCConfig() error { |
7c57a4cf |
fo, err := os.Create(container.lxcConfigPath()) |
a27b4b8c |
if err != nil {
return err
}
defer fo.Close()
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
return err
}
return nil
}
|
caea45dd |
func (container *Container) startPty() error { |
7d889554 |
ptyMaster, ptySlave, err := pty.Open() |
caea45dd |
if err != nil {
return err
} |
7d889554 |
container.ptyMaster = ptyMaster
container.cmd.Stdout = ptySlave
container.cmd.Stderr = ptySlave |
caea45dd |
// Copy the PTYs to our broadcasters
go func() { |
a83d87ab |
defer container.stdout.CloseWriters() |
2e69e172 |
utils.Debugf("[startPty] Begin of stdout pipe") |
7d889554 |
io.Copy(container.stdout, ptyMaster) |
2e69e172 |
utils.Debugf("[startPty] End of stdout pipe") |
caea45dd |
}()
// stdin
if container.Config.OpenStdin { |
7d889554 |
container.cmd.Stdin = ptySlave |
33a5fe3b |
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} |
caea45dd |
go func() {
defer container.stdin.Close() |
2e69e172 |
utils.Debugf("[startPty] Begin of stdin pipe") |
7d889554 |
io.Copy(ptyMaster, container.stdin) |
2e69e172 |
utils.Debugf("[startPty] End of stdin pipe") |
caea45dd |
}()
}
if err := container.cmd.Start(); err != nil {
return err
} |
7d889554 |
ptySlave.Close() |
caea45dd |
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() |
2e69e172 |
utils.Debugf("Begin of stdin pipe [start]") |
caea45dd |
io.Copy(stdin, container.stdin) |
2e69e172 |
utils.Debugf("End of stdin pipe [start]") |
caea45dd |
}()
}
return container.cmd.Start()
}
|
6882c78c |
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
var cStdout, cStderr io.ReadCloser
|
c808940c |
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() { |
2e69e172 |
utils.Debugf("[start] attach stdin\n")
defer utils.Debugf("[end] attach stdin\n") |
ad2bbe23 |
// 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()
} |
64c1b6d9 |
if container.Config.StdinOnce && !container.Config.Tty { |
c808940c |
defer cStdin.Close()
} |
faa88436 |
if container.Config.Tty { |
2e69e172 |
_, err = utils.CopyEscapable(cStdin, stdin) |
faa88436 |
} else {
_, err = io.Copy(cStdin, stdin)
} |
c808940c |
if err != nil { |
2e69e172 |
utils.Debugf("[error] attach stdin: %s\n", err) |
c808940c |
} |
6882c78c |
// Discard error, expecting pipe error
errors <- nil |
c808940c |
}()
}
}
if stdout != nil {
nJobs += 1
if p, err := container.StdoutPipe(); err != nil {
errors <- err
} else {
cStdout = p
go func() { |
2e69e172 |
utils.Debugf("[start] attach stdout\n")
defer utils.Debugf("[end] attach stdout\n") |
6882c78c |
// 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()
}
} |
c808940c |
_, err := io.Copy(stdout, cStdout)
if err != nil { |
2e69e172 |
utils.Debugf("[error] attach stdout: %s\n", err) |
c808940c |
}
errors <- err
}()
} |
0ca88443 |
} else {
go func() { |
63e80384 |
if stdinCloser != nil {
defer stdinCloser.Close()
} |
0ca88443 |
|
63e80384 |
if cStdout, err := container.StdoutPipe(); err != nil { |
0ca88443 |
utils.Debugf("Error stdout pipe") |
63e80384 |
} else {
io.Copy(&utils.NopWriter{}, cStdout) |
0ca88443 |
}
}() |
c808940c |
}
if stderr != nil {
nJobs += 1
if p, err := container.StderrPipe(); err != nil {
errors <- err
} else {
cStderr = p
go func() { |
2e69e172 |
utils.Debugf("[start] attach stderr\n")
defer utils.Debugf("[end] attach stderr\n") |
6882c78c |
// 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()
}
} |
c808940c |
_, err := io.Copy(stderr, cStderr)
if err != nil { |
2e69e172 |
utils.Debugf("[error] attach stderr: %s\n", err) |
c808940c |
}
errors <- err
}()
} |
0ca88443 |
} else {
go func() { |
63e80384 |
if stdinCloser != nil {
defer stdinCloser.Close()
} |
0ca88443 |
|
71692126 |
if cStderr, err := container.StderrPipe(); err != nil { |
0ca88443 |
utils.Debugf("Error stdout pipe") |
63e80384 |
} else {
io.Copy(&utils.NopWriter{}, cStderr) |
0ca88443 |
}
}() |
c808940c |
} |
0ca88443 |
|
2e69e172 |
return utils.Go(func() error { |
c808940c |
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 { |
2e69e172 |
utils.Debugf("Waiting for job %d/%d\n", i+1, nJobs) |
c808940c |
if err := <-errors; err != nil { |
2e69e172 |
utils.Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err) |
c808940c |
return err
} |
2e69e172 |
utils.Debugf("Job %d completed successfully\n", i+1) |
c808940c |
} |
2e69e172 |
utils.Debugf("All jobs completed successfully\n") |
c808940c |
return nil
})
}
|
a27b4b8c |
func (container *Container) Start() error { |
7c2b085d |
container.State.lock()
defer container.State.unlock()
|
d949e280 |
if container.State.Running { |
fd224ee5 |
return fmt.Errorf("The container %s is already running.", container.ID) |
d949e280 |
} |
7c57a4cf |
if err := container.EnsureMounted(); err != nil { |
a27b4b8c |
return err
} |
c08f5b2b |
if err := container.allocateNetwork(); err != nil {
return err
} |
1967c834 |
|
f68d107a |
// Make sure the config is compatible with the current kernel
if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit {
log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") |
1967c834 |
container.Config.Memory = 0
} |
f68d107a |
if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit {
log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
container.Config.MemorySwap = -1
} |
6fb495bf |
container.Volumes = make(map[string]string) |
1967c834 |
|
8d9aaee6 |
// Create the requested volumes volumes
for volPath := range container.Config.Volumes { |
86ada2fa |
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil { |
8d9aaee6 |
return err
} |
86ada2fa |
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
} |
fd224ee5 |
container.Volumes[volPath] = c.ID |
8d9aaee6 |
}
|
4099a313 |
if container.Config.VolumesFrom != "" {
c := container.runtime.Get(container.Config.VolumesFrom)
if c == nil { |
fd224ee5 |
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID) |
4099a313 |
}
for volPath, id := range c.Volumes {
if _, exists := container.Volumes[volPath]; exists { |
fd224ee5 |
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.ID) |
4099a313 |
}
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
container.Volumes[volPath] = id
}
} |
1967c834 |
|
1793538a |
if err := container.generateLXCConfig(); err != nil {
return err
} |
8d9aaee6 |
|
a27b4b8c |
params := []string{ |
fd224ee5 |
"-n", container.ID, |
7c57a4cf |
"-f", container.lxcConfigPath(), |
a27b4b8c |
"--", |
58a22942 |
"/sbin/init", |
a27b4b8c |
} |
5cecd548 |
// Networking |
c08f5b2b |
params = append(params, "-g", container.network.Gateway.String()) |
5cecd548 |
// User |
6de3e8a2 |
if container.Config.User != "" {
params = append(params, "-u", container.Config.User)
} |
5cecd548 |
|
7b0e96f1 |
if container.Config.Tty {
params = append(params, "-e", "TERM=xterm")
}
// Setup environment
params = append(params,
"-e", "HOME=/",
"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
)
for _, elem := range container.Config.Env {
params = append(params, "-e", elem)
}
|
5cecd548 |
// Program |
6de3e8a2 |
params = append(params, "--", container.Path) |
a27b4b8c |
params = append(params, container.Args...) |
5cecd548 |
|
6d72758f |
container.cmd = exec.Command("lxc-start", params...) |
a27b4b8c |
|
0f7a4534 |
// 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
}
|
caea45dd |
var err error |
7a50153c |
if container.Config.Tty { |
caea45dd |
err = container.startPty() |
7a50153c |
} else { |
caea45dd |
err = container.start() |
7a50153c |
} |
caea45dd |
if err != nil { |
a27b4b8c |
return err
} |
7c57a4cf |
// FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we can restore after crash etc. |
a27b4b8c |
container.State.setRunning(container.cmd.Process.Pid) |
329f4449 |
// Init the lock
container.waitLock = make(chan struct{}) |
35d704c8 |
|
7c57a4cf |
container.ToDisk() |
a27b4b8c |
go container.monitor() |
f958bdba |
return nil |
a27b4b8c |
}
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
}
|
7a50153c |
// 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
}
|
a27b4b8c |
func (container *Container) StdoutPipe() (io.ReadCloser, error) {
reader, writer := io.Pipe()
container.stdout.AddWriter(writer) |
2e69e172 |
return utils.NewBufReader(reader), nil |
a27b4b8c |
}
func (container *Container) StderrPipe() (io.ReadCloser, error) {
reader, writer := io.Pipe()
container.stderr.AddWriter(writer) |
2e69e172 |
return utils.NewBufReader(reader), nil |
a27b4b8c |
}
|
c08f5b2b |
func (container *Container) allocateNetwork() error { |
d65983f3 |
iface, err := container.runtime.networkManager.Allocate() |
c08f5b2b |
if err != nil {
return err
} |
09eacdfa |
container.NetworkSettings.PortMapping = make(map[string]string) |
2aad4a34 |
for _, spec := range container.Config.PortSpecs { |
86ada2fa |
nat, err := iface.AllocatePort(spec)
if err != nil { |
09eacdfa |
iface.Release()
return err
} |
86ada2fa |
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend) |
09eacdfa |
} |
c08f5b2b |
container.network = iface |
d9a9bfc9 |
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface |
fd224ee5 |
container.NetworkSettings.IPAddress = iface.IPNet.IP.String()
container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size() |
09eacdfa |
container.NetworkSettings.Gateway = iface.Gateway.String() |
c08f5b2b |
return nil
}
|
54443c09 |
func (container *Container) releaseNetwork() { |
6f9a67a7 |
container.network.Release() |
c08f5b2b |
container.network = nil |
09eacdfa |
container.NetworkSettings = &NetworkSettings{} |
5d6dd22f |
}
|
82848d41 |
// FIXME: replace this with a control socket within docker-init
func (container *Container) waitLxc() error {
for { |
fd224ee5 |
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput() |
86ada2fa |
if err != nil { |
82848d41 |
return err |
86ada2fa |
}
if !strings.Contains(string(output), "RUNNING") {
return nil |
82848d41 |
}
time.Sleep(500 * time.Millisecond)
} |
7ef9833d |
panic("Unreachable") |
82848d41 |
}
|
a27b4b8c |
func (container *Container) monitor() { |
f0c08b57 |
// Wait for the program to exit |
2e69e172 |
utils.Debugf("Waiting for process") |
82848d41 |
// If the command does not exists, try to wait via lxc
if container.cmd == nil {
if err := container.waitLxc(); err != nil { |
fd224ee5 |
utils.Debugf("%s: Process: %s", container.ID, err) |
82848d41 |
}
} else {
if err := container.cmd.Wait(); err != nil {
// Discard the error as any signals or non 0 returns will generate an error |
fd224ee5 |
utils.Debugf("%s: Process: %s", container.ID, err) |
82848d41 |
} |
69c2250e |
} |
2e69e172 |
utils.Debugf("Process finished") |
ccac5b13 |
|
86ada2fa |
exitCode := -1 |
82848d41 |
if container.cmd != nil {
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
} |
24dac228 |
|
f0c08b57 |
// Cleanup |
6f9a67a7 |
container.releaseNetwork() |
47607494 |
if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil { |
fd224ee5 |
utils.Debugf("%s: Error close stdin: %s", container.ID, err) |
47607494 |
}
} |
a83d87ab |
if err := container.stdout.CloseWriters(); err != nil { |
fd224ee5 |
utils.Debugf("%s: Error close stdout: %s", container.ID, err) |
69c2250e |
} |
a83d87ab |
if err := container.stderr.CloseWriters(); err != nil { |
fd224ee5 |
utils.Debugf("%s: Error close stderr: %s", container.ID, err) |
69c2250e |
} |
5252ab69 |
|
7d889554 |
if container.ptyMaster != nil {
if err := container.ptyMaster.Close(); err != nil { |
fd224ee5 |
utils.Debugf("%s: Error closing Pty master: %s", container.ID, err) |
5252ab69 |
}
}
|
7c57a4cf |
if err := container.Unmount(); err != nil { |
fd224ee5 |
log.Printf("%v: Failed to umount filesystem: %v", container.ID, err) |
24dac228 |
}
|
0da9ccc1 |
// Re-create a brand new stdin pipe once the container exited
if container.Config.OpenStdin {
container.stdin, container.stdinPipe = io.Pipe()
}
|
24dac228 |
// Report status back
container.State.setStopped(exitCode) |
329f4449 |
// Release the lock
close(container.waitLock)
|
69c2250e |
if err := container.ToDisk(); err != nil { |
a52a28b6 |
// 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? |
fd224ee5 |
//log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err) |
69c2250e |
} |
a27b4b8c |
}
|
24dac228 |
func (container *Container) kill() error { |
d440782e |
if !container.State.Running { |
b32436cd |
return nil
} |
e68c04b7 |
|
bb22cd49 |
// Sending SIGKILL to the process via lxc |
fd224ee5 |
output, err := exec.Command("lxc-kill", "-n", container.ID, "9").CombinedOutput() |
e68c04b7 |
if err != nil { |
fd224ee5 |
log.Printf("error killing container %s (%s, %s)", container.ID, output, err) |
a27b4b8c |
} |
e68c04b7 |
// 2. Wait for the process to die, in last resort, try to kill the process directly
if err := container.WaitTimeout(10 * time.Second); err != nil { |
d440782e |
if container.cmd == nil { |
fd224ee5 |
return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ID) |
d440782e |
} |
fd224ee5 |
log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.ID) |
e68c04b7 |
if err := container.cmd.Process.Kill(); err != nil {
return err
}
}
|
24dac228 |
// Wait for the container to be actually stopped |
f958bdba |
container.Wait() |
24dac228 |
return nil
}
func (container *Container) Kill() error { |
7c2b085d |
container.State.lock()
defer container.State.unlock() |
313d13ea |
if !container.State.Running {
return nil
} |
24dac228 |
return container.kill()
}
|
1615bb08 |
func (container *Container) Stop(seconds int) error { |
7c2b085d |
container.State.lock()
defer container.State.unlock() |
24dac228 |
if !container.State.Running { |
a27b4b8c |
return nil
} |
24dac228 |
// 1. Send a SIGTERM |
fd224ee5 |
if output, err := exec.Command("lxc-kill", "-n", container.ID, "15").CombinedOutput(); err != nil { |
c298a91f |
log.Print(string(output))
log.Print("Failed to send SIGTERM to the process, force killing") |
7c2b085d |
if err := container.kill(); err != nil { |
f4e25694 |
return err
} |
24dac228 |
}
// 2. Wait for the process to exit on its own |
1615bb08 |
if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil { |
fd224ee5 |
log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds) |
0d9e5436 |
if err := container.kill(); err != nil { |
f4e25694 |
return err
} |
24dac228 |
} |
a27b4b8c |
return nil
}
|
1615bb08 |
func (container *Container) Restart(seconds int) error {
if err := container.Stop(seconds); err != nil { |
f0c08b57 |
return err
}
if err := container.Start(); err != nil {
return err
}
return nil
}
|
ebaa50c4 |
// Wait blocks until the container stops running, then returns its exit code.
func (container *Container) Wait() int { |
329f4449 |
<-container.waitLock |
ebaa50c4 |
return container.State.ExitCode |
a27b4b8c |
} |
24dac228 |
|
70d2123e |
func (container *Container) Resize(h, w int) error { |
88ef309a |
pty, ok := container.ptyMaster.(*os.File)
if !ok {
return fmt.Errorf("ptyMaster does not have Fd() method")
}
return term.SetWinsize(pty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) |
70d2123e |
}
|
ef711962 |
func (container *Container) ExportRw() (Archive, error) {
return Tar(container.rwPath(), Uncompressed) |
7c57a4cf |
}
|
048fd671 |
func (container *Container) RwChecksum() (string, error) { |
be756089 |
rwData, err := Tar(container.rwPath(), Xz) |
048fd671 |
if err != nil {
return "", err
} |
2e69e172 |
return utils.HashData(rwData) |
048fd671 |
}
|
ef711962 |
func (container *Container) Export() (Archive, error) { |
7c57a4cf |
if err := container.EnsureMounted(); err != nil {
return nil, err
} |
ef711962 |
return Tar(container.RootfsPath(), Uncompressed) |
7c57a4cf |
}
|
24dac228 |
func (container *Container) WaitTimeout(timeout time.Duration) error {
done := make(chan bool)
go func() {
container.Wait()
done <- true
}()
select {
case <-time.After(timeout): |
d17f78c3 |
return fmt.Errorf("Timed Out") |
24dac228 |
case <-done:
return nil
} |
7ef9833d |
panic("Unreachable") |
24dac228 |
} |
7c57a4cf |
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())
}
|
ef711962 |
func (container *Container) Changes() ([]Change, error) { |
7c57a4cf |
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.Changes(container.rwPath())
}
|
ef711962 |
func (container *Container) GetImage() (*Image, error) { |
7c57a4cf |
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) { |
ef711962 |
return Mounted(container.RootfsPath()) |
7c57a4cf |
}
func (container *Container) Unmount() error { |
ef711962 |
return Unmount(container.RootfsPath()) |
7c57a4cf |
}
|
fd224ee5 |
// ShortID returns a shorthand version of the container's id for convenience. |
0b9a3c86 |
// 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. |
fd224ee5 |
func (container *Container) ShortID() string {
return utils.TruncateID(container.ID) |
0b9a3c86 |
}
|
7c57a4cf |
func (container *Container) logPath(name string) string { |
fd224ee5 |
return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name)) |
7c57a4cf |
}
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")
}
|
6fb495bf |
func (container *Container) GetVolumes() (map[string]string, error) {
ret := make(map[string]string)
for volPath, id := range container.Volumes {
volume, err := container.runtime.volumes.Get(id)
if err != nil {
return nil, err
}
root, err := volume.root()
if err != nil {
return nil, err
}
ret[volPath] = path.Join(root, "layer")
}
return ret, nil
}
|
7c57a4cf |
func (container *Container) rwPath() string {
return path.Join(container.root, "rw")
}
|
fd224ee5 |
func validateID(id string) error { |
7c57a4cf |
if id == "" {
return fmt.Errorf("Invalid empty id")
}
return nil
} |
a91b7109 |
// GetSize, return real size, virtual size
func (container *Container) GetSize() (int64, int64) {
var sizeRw, sizeRootfs int64
filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo != nil {
sizeRw += fileInfo.Size()
}
return nil
})
_, err := os.Stat(container.RootfsPath())
if err == nil {
filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo != nil {
sizeRootfs += fileInfo.Size()
}
return nil
})
}
return sizeRw, sizeRootfs
} |