moby/container.go

406 lines
9.1 KiB
Go
Raw Normal View History

2013-01-19 00:13:39 +00:00
package docker
import (
2013-01-28 22:30:05 +00:00
"bytes"
2013-01-19 00:13:39 +00:00
"encoding/json"
"errors"
"io"
"io/ioutil"
"log"
2013-01-19 00:13:39 +00:00
"os"
"os/exec"
"path"
2013-01-28 22:30:05 +00:00
"strings"
2013-01-19 00:13:39 +00:00
"syscall"
"time"
"github.com/kr/pty"
2013-01-19 00:13:39 +00:00
)
type Container struct {
2013-01-22 02:39:52 +00:00
Id string
2013-01-19 00:13:39 +00:00
Root string
Created time.Time
2013-01-19 00:13:39 +00:00
Path string
Args []string
Config *Config
Filesystem *Filesystem
State *State
2013-01-19 00:13:39 +00:00
lxcConfigPath string
cmd *exec.Cmd
stdout *writeBroadcaster
stderr *writeBroadcaster
stdin io.ReadCloser
stdinPipe io.WriteCloser
2013-01-28 22:30:05 +00:00
stdoutLog *bytes.Buffer
stderrLog *bytes.Buffer
2013-01-19 00:13:39 +00:00
}
type Config struct {
Hostname string
Ram int64
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
2013-01-19 00:13:39 +00:00
}
2013-01-22 02:39:52 +00:00
func createContainer(id string, root string, command string, args []string, layers []string, config *Config) (*Container, error) {
2013-01-19 00:13:39 +00:00
container := &Container{
2013-01-22 02:39:52 +00:00
Id: id,
2013-01-19 00:13:39 +00:00
Root: root,
Created: time.Now(),
2013-01-19 00:13:39 +00:00
Path: command,
Args: args,
Config: config,
Filesystem: newFilesystem(path.Join(root, "rootfs"), path.Join(root, "rw"), layers),
State: newState(),
lxcConfigPath: path.Join(root, "config.lxc"),
stdout: newWriteBroadcaster(),
stderr: newWriteBroadcaster(),
2013-01-28 22:30:05 +00:00
stdoutLog: new(bytes.Buffer),
stderrLog: new(bytes.Buffer),
2013-01-19 00:13:39 +00:00
}
if container.Config.OpenStdin {
container.stdin, container.stdinPipe = io.Pipe()
} else {
container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
}
container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
2013-01-19 00:13:39 +00:00
if err := os.Mkdir(root, 0700); err != nil {
return nil, err
}
if err := container.Filesystem.createMountPoints(); err != nil {
return nil, err
}
2013-01-19 00:13:39 +00:00
if err := container.save(); err != nil {
return nil, err
}
if err := container.generateLXCConfig(); err != nil {
return nil, err
}
return container, nil
}
func loadContainer(containerPath string) (*Container, error) {
data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
2013-01-19 00:13:39 +00:00
if err != nil {
return nil, err
}
container := &Container{
2013-01-28 22:30:05 +00:00
stdout: newWriteBroadcaster(),
stderr: newWriteBroadcaster(),
stdoutLog: new(bytes.Buffer),
stderrLog: new(bytes.Buffer),
}
if err := json.Unmarshal(data, container); err != nil {
2013-01-19 00:13:39 +00:00
return nil, err
}
if err := container.Filesystem.createMountPoints(); err != nil {
return nil, err
}
if container.Config.OpenStdin {
container.stdin, container.stdinPipe = io.Pipe()
} else {
container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
}
container.State = newState()
2013-01-19 00:13:39 +00:00
return container, nil
}
func (container *Container) Cmd() *exec.Cmd {
return container.cmd
}
func (container *Container) loadUserData() (map[string]string, error) {
jsonData, err := ioutil.ReadFile(path.Join(container.Root, "userdata.json"))
if err != nil {
if os.IsNotExist(err) {
return make(map[string]string), nil
}
return nil, err
}
data := make(map[string]string)
if err := json.Unmarshal(jsonData, &data); err != nil {
return nil, err
}
return data, nil
}
func (container *Container) saveUserData(data map[string]string) error {
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
return ioutil.WriteFile(path.Join(container.Root, "userdata.json"), jsonData, 0700)
}
func (container *Container) SetUserData(key, value string) error {
data, err := container.loadUserData()
if err != nil {
return err
}
data[key] = value
return container.saveUserData(data)
}
2013-01-28 22:30:05 +00:00
func (container *Container) GetUserData(key string) string {
data, err := container.loadUserData()
if err != nil {
return ""
}
if value, exists := data[key]; exists {
return value
}
return ""
}
func (container *Container) save() (err error) {
data, err := json.Marshal(container)
2013-01-19 00:13:39 +00:00
if err != nil {
return
2013-01-19 00:13:39 +00:00
}
return ioutil.WriteFile(path.Join(container.Root, "config.json"), data, 0700)
2013-01-19 00:13:39 +00:00
}
func (container *Container) generateLXCConfig() error {
fo, err := os.Create(container.lxcConfigPath)
if err != nil {
return err
}
defer fo.Close()
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
return err
}
return nil
}
func (container *Container) Start() error {
if err := container.Filesystem.EnsureMounted(); err != nil {
2013-01-19 00:13:39 +00:00
return err
}
params := []string{
2013-01-22 02:39:52 +00:00
"-n", container.Id,
2013-01-19 00:13:39 +00:00
"-f", container.lxcConfigPath,
"--",
container.Path,
}
params = append(params, container.Args...)
container.cmd = exec.Command("/usr/bin/lxc-start", params...)
if container.Config.Tty {
Pty, tty, err := pty.Open()
if err != nil {
return err
}
container.cmd.Stdout = tty
container.cmd.Stderr = tty
if container.stdin != nil {
container.cmd.Stdin = tty
}
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
if err := container.cmd.Start(); err != nil {
Pty.Close()
return err
}
tty.Close()
// Attach Pty to stdout
go func() {
defer container.stdout.Close()
for {
data := make([]byte, 1024)
n, err := Pty.Read(data)
if err != nil {
return
}
log.Printf("STDOUT <%s>\n", data)
if _, err = container.stdout.Write(data[:n]); err != nil {
return
}
log.Printf("STDOUT SENT\n")
}
//io.Copy(container.stdout, Pty)
//container.stdout.Close()
}()
// Attach Pty to stderr
go func() {
io.Copy(container.stderr, Pty)
container.stderr.Close()
}()
// Attach Pty to stdin
if container.stdin != nil {
go func() {
defer Pty.Close()
io.Copy(Pty, container.stdin)
}()
}
} else {
container.cmd.Stdout = container.stdout
container.cmd.Stderr = container.stderr
if container.stdin != nil {
stdin, err := container.cmd.StdinPipe()
if err != nil {
return err
}
go func() {
defer stdin.Close()
io.Copy(stdin, container.stdin)
}()
}
}
2013-01-19 00:13:39 +00:00
if err := container.cmd.Start(); err != nil {
return err
}
container.State.setRunning(container.cmd.Process.Pid)
container.save()
2013-01-19 00:13:39 +00:00
go container.monitor()
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
}
// 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) StdoutLog() io.Reader {
return strings.NewReader(container.stdoutLog.String())
}
2013-01-19 00:13:39 +00:00
func (container *Container) StderrPipe() (io.ReadCloser, error) {
reader, writer := io.Pipe()
container.stderr.AddWriter(writer)
return newBufReader(reader), nil
}
func (container *Container) StderrLog() io.Reader {
return strings.NewReader(container.stderrLog.String())
}
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-01-19 00:13:39 +00:00
container.cmd.Wait()
exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
2013-01-22 23:03:40 +00:00
// Cleanup
2013-01-19 00:13:39 +00:00
container.stdout.Close()
container.stderr.Close()
if err := container.Filesystem.Umount(); err != nil {
2013-01-22 02:39:52 +00:00
log.Printf("%v: Failed to umount filesystem: %v", container.Id, err)
}
// Report status back
container.State.setStopped(exitCode)
container.save()
2013-01-19 00:13:39 +00:00
}
func (container *Container) kill() error {
if err := container.cmd.Process.Kill(); err != nil {
return err
2013-01-19 00:13:39 +00:00
}
// Wait for the container to be actually stopped
container.Wait()
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
}
// 1. Send a SIGTERM
if output, err := exec.Command("/usr/bin/lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
log.Printf(string(output))
log.Printf("Failed to send SIGTERM to the process, force killing")
if err := container.Kill(); err != nil {
return err
}
}
// 2. Wait for the process to exit on its own
if err := container.WaitTimeout(10 * time.Second); err != nil {
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-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-01-19 00:13:39 +00:00
func (container *Container) Wait() {
2013-01-19 00:13:39 +00:00
for container.State.Running {
container.State.wait()
}
}
func (container *Container) WaitTimeout(timeout time.Duration) error {
done := make(chan bool)
go func() {
container.Wait()
done <- true
}()
select {
case <-time.After(timeout):
return errors.New("Timed Out")
case <-done:
return nil
}
return nil
}