Merge github.com:dotcloud/docker into 333-redis-documentation

This commit is contained in:
John Costa 2013-04-09 16:07:40 -04:00
Родитель 8f15c423e6 40ebe78bb1
Коммит 418ef43fbb
27 изменённых файлов: 594 добавлений и 191 удалений

Просмотреть файл

@ -134,6 +134,12 @@ docker pull base
docker run -i -t base /bin/bash docker run -i -t base /bin/bash
``` ```
Detaching from the interactive shell
------------------------------------
```
# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q
# Note: this works only in tty mode (run with -t option).
```
Starting a long-running worker process Starting a long-running worker process
-------------------------------------- --------------------------------------
@ -183,7 +189,9 @@ JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
PORT=$(docker port $JOB 4444) PORT=$(docker port $JOB 4444)
# Connect to the public port via the host's public address # Connect to the public port via the host's public address
echo hello world | nc $(hostname) $PORT # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
echo hello world | nc $IP $PORT
# Verify that the network connection worked # Verify that the network connection worked
echo "Daemon received: $(docker logs $JOB)" echo "Daemon received: $(docker logs $JOB)"

Просмотреть файл

@ -18,7 +18,7 @@ import (
"unicode" "unicode"
) )
const VERSION = "0.1.3" const VERSION = "0.1.4"
var GIT_COMMIT string var GIT_COMMIT string
@ -62,7 +62,7 @@ func (srv *Server) Help() string {
} }
// 'docker login': login / register a user to registry service. // 'docker login': login / register a user to registry service.
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
// Read a line on raw terminal with support for simple backspace // Read a line on raw terminal with support for simple backspace
// sequences and echo. // sequences and echo.
// //
@ -113,6 +113,8 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
return readStringOnRawTerminal(stdin, stdout, false) return readStringOnRawTerminal(stdin, stdout, false)
} }
stdout.SetOptionRawTerminal()
cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server") cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil return nil
@ -417,7 +419,8 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
return nil return nil
} }
func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
stdout.Flush()
cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
var archive io.Reader var archive io.Reader
var resp *http.Response var resp *http.Response
@ -464,7 +467,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
return nil return nil
} }
func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry") cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil return nil
@ -784,7 +787,7 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
return fmt.Errorf("No such container: %s", cmd.Arg(0)) return fmt.Errorf("No such container: %s", cmd.Arg(0))
} }
func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container") cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil return nil
@ -799,6 +802,11 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
return fmt.Errorf("No such container: %s", name) return fmt.Errorf("No such container: %s", name)
} }
if container.Config.Tty {
stdout.SetOptionRawTerminal()
}
// Flush the options to make sure the client sets the raw mode
stdout.Flush()
return <-container.Attach(stdin, nil, stdout, stdout) return <-container.Attach(stdin, nil, stdout, stdout)
} }
@ -870,7 +878,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force) return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force)
} }
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
config, err := ParseRun(args, stdout) config, err := ParseRun(args, stdout)
if err != nil { if err != nil {
return err return err
@ -884,6 +892,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
return fmt.Errorf("Command not specified") return fmt.Errorf("Command not specified")
} }
if config.Tty {
stdout.SetOptionRawTerminal()
}
// Flush the options to make sure the client sets the raw mode
// or tell the client there is no options
stdout.Flush()
// Create new container // Create new container
container, err := srv.runtime.Create(config) container, err := srv.runtime.Create(config)
if err != nil { if err != nil {

Просмотреть файл

@ -2,8 +2,8 @@ package docker
import ( import (
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"github.com/dotcloud/docker/rcli"
"io" "io"
"io/ioutil" "io/ioutil"
"strings" "strings"
@ -69,15 +69,27 @@ func TestRunHostname(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
var stdin, stdout bytes.Buffer stdin, _ := io.Pipe()
setTimeout(t, "CmdRun timed out", 2*time.Second, func() { stdout, stdoutPipe := io.Pipe()
if err := srv.CmdRun(ioutil.NopCloser(&stdin), &nopWriteCloser{&stdout}, "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
c := make(chan struct{})
go func() {
if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}) close(c)
if output := string(stdout.Bytes()); output != "foobar\n" { }()
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", output) cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
} }
if cmdOutput != "foobar\n" {
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
}
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
<-c
})
} }
func TestRunExit(t *testing.T) { func TestRunExit(t *testing.T) {
@ -93,7 +105,7 @@ func TestRunExit(t *testing.T) {
stdout, stdoutPipe := io.Pipe() stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{}) c1 := make(chan struct{})
go func() { go func() {
srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat") srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
close(c1) close(c1)
}() }()
@ -147,7 +159,7 @@ func TestRunDisconnect(t *testing.T) {
go func() { go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the // We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdRun returns. // fact that CmdRun returns.
srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat") srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
close(c1) close(c1)
}() }()
@ -179,10 +191,56 @@ func TestRunDisconnect(t *testing.T) {
}) })
} }
// Expected behaviour: the process dies when the client disconnects
func TestRunDisconnectTty(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{})
go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdRun returns.
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", GetTestImage(runtime).Id, "/bin/cat")
close(c1)
}()
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
})
// Close pipes (simulate disconnect)
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
// as the pipes are close, we expect the process to die,
// therefore CmdRun to unblock. Wait for CmdRun
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
<-c1
})
// Client disconnect after run -i should keep stdin out in TTY mode
container := runtime.List()[0]
// Give some time to monitor to do his thing
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
}
}
// TestAttachStdin checks attaching to stdin without stdout and stderr. // TestAttachStdin checks attaching to stdin without stdout and stderr.
// 'docker run -i -a stdin' should sends the client's stdin to the command, // 'docker run -i -a stdin' should sends the client's stdin to the command,
// then detach from it and print the container id. // then detach from it and print the container id.
func TestAttachStdin(t *testing.T) { func TestRunAttachStdin(t *testing.T) {
runtime, err := newTestRuntime() runtime, err := newTestRuntime()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -190,31 +248,41 @@ func TestAttachStdin(t *testing.T) {
defer nuke(runtime) defer nuke(runtime)
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
stdinR, stdinW := io.Pipe() stdin, stdinPipe := io.Pipe()
var stdout bytes.Buffer stdout, stdoutPipe := io.Pipe()
ch := make(chan struct{}) ch := make(chan struct{})
go func() { go func() {
srv.CmdRun(stdinR, &stdout, "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat") srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
close(ch) close(ch)
}() }()
// Send input to the command, close stdin, wait for CmdRun to return // Send input to the command, close stdin
setTimeout(t, "Read/Write timed out", 2*time.Second, func() { setTimeout(t, "Write timed out", 2*time.Second, func() {
if _, err := stdinW.Write([]byte("hi there\n")); err != nil { if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
t.Fatal(err)
}
if err := stdinPipe.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
stdinW.Close()
<-ch
}) })
// Check output
cmdOutput := string(stdout.Bytes())
container := runtime.List()[0] container := runtime.List()[0]
// Check output
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != container.ShortId()+"\n" { if cmdOutput != container.ShortId()+"\n" {
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput) t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
} }
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
<-ch
})
setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() { setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
container.Wait() container.Wait()
}) })
@ -270,7 +338,7 @@ func TestAttachDisconnect(t *testing.T) {
go func() { go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the // We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdAttach returns. // fact that CmdAttach returns.
srv.CmdAttach(stdin, stdoutPipe, container.Id) srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
close(c1) close(c1)
}() }()

Просмотреть файл

@ -40,11 +40,11 @@ type Container struct {
stdin io.ReadCloser stdin io.ReadCloser
stdinPipe io.WriteCloser stdinPipe io.WriteCloser
ptyStdinMaster io.Closer ptyMaster io.Closer
ptyStdoutMaster io.Closer
ptyStderrMaster io.Closer
runtime *Runtime runtime *Runtime
waitLock chan struct{}
} }
type Config struct { type Config struct {
@ -180,63 +180,37 @@ func (container *Container) generateLXCConfig() error {
} }
func (container *Container) startPty() error { func (container *Container) startPty() error {
stdoutMaster, stdoutSlave, err := pty.Open() ptyMaster, ptySlave, err := pty.Open()
if err != nil { if err != nil {
return err return err
} }
container.ptyStdoutMaster = stdoutMaster container.ptyMaster = ptyMaster
container.cmd.Stdout = stdoutSlave container.cmd.Stdout = ptySlave
container.cmd.Stderr = ptySlave
stderrMaster, stderrSlave, err := pty.Open()
if err != nil {
return err
}
container.ptyStderrMaster = stderrMaster
container.cmd.Stderr = stderrSlave
// Copy the PTYs to our broadcasters // Copy the PTYs to our broadcasters
go func() { go func() {
defer container.stdout.CloseWriters() defer container.stdout.CloseWriters()
Debugf("[startPty] Begin of stdout pipe") Debugf("[startPty] Begin of stdout pipe")
io.Copy(container.stdout, stdoutMaster) io.Copy(container.stdout, ptyMaster)
Debugf("[startPty] End of stdout pipe") Debugf("[startPty] End of stdout pipe")
}() }()
go func() {
defer container.stderr.CloseWriters()
Debugf("[startPty] Begin of stderr pipe")
io.Copy(container.stderr, stderrMaster)
Debugf("[startPty] End of stderr pipe")
}()
// stdin // stdin
var stdinSlave io.ReadCloser
if container.Config.OpenStdin { if container.Config.OpenStdin {
var stdinMaster io.WriteCloser container.cmd.Stdin = ptySlave
stdinMaster, stdinSlave, err = pty.Open() container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
if err != nil {
return err
}
container.ptyStdinMaster = stdinMaster
container.cmd.Stdin = stdinSlave
// FIXME: The following appears to be broken.
// "cannot set terminal process group (-1): Inappropriate ioctl for device"
// container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
go func() { go func() {
defer container.stdin.Close() defer container.stdin.Close()
Debugf("[startPty] Begin of stdin pipe") Debugf("[startPty] Begin of stdin pipe")
io.Copy(stdinMaster, container.stdin) io.Copy(ptyMaster, container.stdin)
Debugf("[startPty] End of stdin pipe") Debugf("[startPty] End of stdin pipe")
}() }()
} }
if err := container.cmd.Start(); err != nil { if err := container.cmd.Start(); err != nil {
return err return err
} }
stdoutSlave.Close() ptySlave.Close()
stderrSlave.Close()
if stdinSlave != nil {
stdinSlave.Close()
}
return nil return nil
} }
@ -278,10 +252,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
if cStderr != nil { if cStderr != nil {
defer cStderr.Close() defer cStderr.Close()
} }
if container.Config.StdinOnce { if container.Config.StdinOnce && !container.Config.Tty {
defer cStdin.Close() defer cStdin.Close()
} }
_, err := io.Copy(cStdin, stdin) if container.Config.Tty {
_, err = CopyEscapable(cStdin, stdin)
} else {
_, err = io.Copy(cStdin, stdin)
}
if err != nil { if err != nil {
Debugf("[error] attach stdin: %s\n", err) Debugf("[error] attach stdin: %s\n", err)
} }
@ -365,6 +343,9 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
} }
func (container *Container) Start() error { func (container *Container) Start() error {
container.State.lock()
defer container.State.unlock()
if container.State.Running { if container.State.Running {
return fmt.Errorf("The container %s is already running.", container.Id) return fmt.Errorf("The container %s is already running.", container.Id)
} }
@ -431,6 +412,9 @@ func (container *Container) Start() error {
// FIXME: save state on disk *first*, then converge // FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we can restore after crash etc. // this way disk state is used as a journal, eg. we can restore after crash etc.
container.State.setRunning(container.cmd.Process.Pid) container.State.setRunning(container.cmd.Process.Pid)
// Init the lock
container.waitLock = make(chan struct{})
container.ToDisk() container.ToDisk()
go container.monitor() go container.monitor()
return nil return nil
@ -530,19 +514,9 @@ func (container *Container) monitor() {
Debugf("%s: Error close stderr: %s", container.Id, err) Debugf("%s: Error close stderr: %s", container.Id, err)
} }
if container.ptyStdinMaster != nil { if container.ptyMaster != nil {
if err := container.ptyStdinMaster.Close(); err != nil { if err := container.ptyMaster.Close(); err != nil {
Debugf("%s: Error close pty stdin master: %s", container.Id, err) Debugf("%s: Error closing Pty 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)
} }
} }
@ -557,6 +531,10 @@ func (container *Container) monitor() {
// Report status back // Report status back
container.State.setStopped(exitCode) container.State.setStopped(exitCode)
// Release the lock
close(container.waitLock)
if err := container.ToDisk(); err != nil { if err := container.ToDisk(); err != nil {
// FIXME: there is a race condition here which causes this to fail during the unit tests. // 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 // If another goroutine was waiting for Wait() to return before removing the container's root
@ -569,7 +547,7 @@ func (container *Container) monitor() {
} }
func (container *Container) kill() error { func (container *Container) kill() error {
if container.cmd == nil { if !container.State.Running || container.cmd == nil {
return nil return nil
} }
if err := container.cmd.Process.Kill(); err != nil { if err := container.cmd.Process.Kill(); err != nil {
@ -581,13 +559,14 @@ func (container *Container) kill() error {
} }
func (container *Container) Kill() error { func (container *Container) Kill() error {
if !container.State.Running { container.State.lock()
return nil defer container.State.unlock()
}
return container.kill() return container.kill()
} }
func (container *Container) Stop() error { func (container *Container) Stop() error {
container.State.lock()
defer container.State.unlock()
if !container.State.Running { if !container.State.Running {
return nil return nil
} }
@ -596,7 +575,7 @@ func (container *Container) Stop() error {
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
log.Print(string(output)) log.Print(string(output))
log.Print("Failed to send SIGTERM to the process, force killing") log.Print("Failed to send SIGTERM to the process, force killing")
if err := container.Kill(); err != nil { if err := container.kill(); err != nil {
return err return err
} }
} }
@ -604,7 +583,7 @@ func (container *Container) Stop() error {
// 2. Wait for the process to exit on its own // 2. Wait for the process to exit on its own
if err := container.WaitTimeout(10 * time.Second); err != nil { 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) log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
if err := container.Kill(); err != nil { if err := container.kill(); err != nil {
return err return err
} }
} }
@ -623,10 +602,7 @@ func (container *Container) Restart() error {
// Wait blocks until the container stops running, then returns its exit code. // Wait blocks until the container stops running, then returns its exit code.
func (container *Container) Wait() int { func (container *Container) Wait() int {
<-container.waitLock
for container.State.Running {
container.State.wait()
}
return container.State.ExitCode return container.State.ExitCode
} }

Просмотреть файл

@ -267,6 +267,7 @@ func TestStart(t *testing.T) {
// Try to avoid the timeoout in destroy. Best effort, don't check error // Try to avoid the timeoout in destroy. Best effort, don't check error
cStdin, _ := container.StdinPipe() cStdin, _ := container.StdinPipe()
cStdin.Close() cStdin.Close()
container.WaitTimeout(2 * time.Second)
} }
func TestRun(t *testing.T) { func TestRun(t *testing.T) {

Просмотреть файл

@ -8,7 +8,6 @@ import (
"io" "io"
"log" "log"
"os" "os"
"os/signal"
) )
var GIT_COMMIT string var GIT_COMMIT string
@ -57,29 +56,21 @@ func daemon() error {
} }
func runCommand(args []string) error { func runCommand(args []string) error {
var oldState *term.State
var err error
if term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
oldState, err = term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return err
}
defer term.Restore(int(os.Stdin.Fd()), oldState)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for _ = range c {
term.Restore(int(os.Stdin.Fd()), oldState)
log.Printf("\nSIGINT received\n")
os.Exit(0)
}
}()
}
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
// CloseWrite(), which we need to cleanly signal that stdin is closed without // CloseWrite(), which we need to cleanly signal that stdin is closed without
// closing the connection. // closing the connection.
// See http://code.google.com/p/go/issues/detail?id=3345 // See http://code.google.com/p/go/issues/detail?id=3345
if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil { if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
options := conn.GetOptions()
if options.RawTerminal &&
term.IsTerminal(int(os.Stdin.Fd())) &&
os.Getenv("NORAW") == "" {
if oldState, err := rcli.SetRawTerminal(); err != nil {
return err
} else {
defer rcli.RestoreTerminal(oldState)
}
}
receiveStdout := docker.Go(func() error { receiveStdout := docker.Go(func() error {
_, err := io.Copy(os.Stdout, conn) _, err := io.Copy(os.Stdout, conn)
return err return err
@ -104,12 +95,11 @@ func runCommand(args []string) error {
if err != nil { if err != nil {
return err return err
} }
if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil { dockerConn := rcli.NewDockerLocalConn(os.Stdout)
defer dockerConn.Close()
if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil {
return err return err
} }
} }
if oldState != nil {
term.Restore(int(os.Stdin.Fd()), oldState)
}
return nil return nil
} }

Просмотреть файл

@ -40,3 +40,35 @@ Notes
So changes to those pages should be made directly in html So changes to those pages should be made directly in html
* For the template the css is compiled from less. When changes are needed they can be compiled using * For the template the css is compiled from less. When changes are needed they can be compiled using
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css`` lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
Guides on using sphinx
----------------------
* To make links to certain pages create a link target like so:
```
.. _hello_world:
Hello world
===========
This is.. (etc.)
```
The ``_hello_world:`` will make it possible to link to this position (page and marker) from all other pages.
* Notes, warnings and alarms
```
# a note (use when something is important)
.. note::
# a warning (orange)
.. warning::
# danger (red, use sparsely)
.. danger::
* Code examples
Start without $, so it's easy to copy and paste.

Просмотреть файл

@ -69,7 +69,8 @@ Expose a service on a TCP port
# Connect to the public port via the host's public address # Connect to the public port via the host's public address
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work. # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
echo hello world | nc $(hostname) $PORT IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
echo hello world | nc $IP $PORT
# Verify that the network connection worked # Verify that the network connection worked
echo "Daemon received: $(docker logs $JOB)" echo "Daemon received: $(docker logs $JOB)"

Просмотреть файл

@ -0,0 +1,4 @@
.. note::
This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`

Просмотреть файл

@ -6,8 +6,10 @@
Hello World Hello World
=========== ===========
This is the most basic example available for using Docker. The example assumes you have Docker installed.
.. include:: example_header.inc
This is the most basic example available for using Docker.
Download the base container Download the base container

Просмотреть файл

@ -6,6 +6,9 @@
Hello World Daemon Hello World Daemon
================== ==================
.. include:: example_header.inc
The most boring daemon ever written. The most boring daemon ever written.
This example assumes you have Docker installed and with the base image already imported ``docker pull base``. This example assumes you have Docker installed and with the base image already imported ``docker pull base``.
@ -18,7 +21,7 @@ out every second. It will continue to do this until we stop it.
CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done") CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
We are going to run a simple hello world daemon in a new container made from the busybox daemon. We are going to run a simple hello world daemon in a new container made from the base image.
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon. - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
- **"base"** is the image we want to run the command inside of. - **"base"** is the image we want to run the command inside of.

Просмотреть файл

@ -12,6 +12,7 @@ Contents:
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
running_examples
hello_world hello_world
hello_world_daemon hello_world_daemon
python_web_app python_web_app

Просмотреть файл

@ -6,6 +6,9 @@
Building a python web app Building a python web app
========================= =========================
.. include:: example_header.inc
The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image. The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image.
**Steps:** **Steps:**
@ -45,6 +48,11 @@ Save the changed we just made in the container to a new image called "_/builds/g
WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp) WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
**"-p 5000"* the web app is going to listen on this port, so it must be mapped from the container to the host system.
- **"$BUILD_IMG"** is the image we want to run the command inside of.
- **/usr/local/bin/runapp** is the command which starts the web app.
Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable. Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable.
.. code-block:: bash .. code-block:: bash
@ -54,6 +62,18 @@ Use the new image we just created and create a new container with network port 5
view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output. view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
.. code-block:: bash
WEB_PORT=$(docker port $WEB_WORKER 5000)
lookup the public-facing port which is NAT-ed store the private port used by the container and store it inside of the WEB_PORT variable.
.. code-block:: bash
curl http://`hostname`:$WEB_PORT
Hello world!
access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.
**Video:** **Video:**

Просмотреть файл

@ -0,0 +1,33 @@
:title: Running the Examples
:description: An overview on how to run the docker examples
:keywords: docker, examples, how to
.. _running_examples:
Running The Examples
--------------------
There are two ways to run docker, daemon mode and standalone mode.
When you run the docker command it will first check if there is a docker daemon running in the background it can connect to.
* If it exists it will use that daemon to run all of the commands.
* If it does not exist docker will run in standalone mode (docker will exit after each command).
Docker needs to be run from a privileged account (root).
1. The most common (and recommended) way is to run a docker daemon as root in the background, and then connect to it from the docker client from any account.
.. code-block:: bash
# starting docker daemon in the background
sudo docker -d &
# now you can run docker commands from any account.
docker <command>
2. Standalone: You need to run every command as root, or using sudo
.. code-block:: bash
sudo docker <command>

Просмотреть файл

@ -7,7 +7,7 @@
Create an ssh daemon service Create an ssh daemon service
============================ ============================
.. include:: example_header.inc
**Video:** **Video:**

23
docs/theme/docker/static/css/main.css поставляемый
Просмотреть файл

@ -82,7 +82,7 @@ h4 {
.btn-custom { .btn-custom {
background-color: #292929 !important; background-color: #292929 !important;
background-repeat: repeat-x; background-repeat: repeat-x;
filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828"); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828)); background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
background-image: -moz-linear-gradient(top, #515151, #282828); background-image: -moz-linear-gradient(top, #515151, #282828);
background-image: -ms-linear-gradient(top, #515151, #282828); background-image: -ms-linear-gradient(top, #515151, #282828);
@ -131,6 +131,27 @@ section.header {
margin: 15px 15px 15px 0; margin: 15px 15px 15px 0;
border: 2px solid gray; border: 2px solid gray;
} }
.admonition {
padding: 10px;
border: 1px solid grey;
margin-bottom: 10px;
margin-top: 10px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.admonition .admonition-title {
font-weight: bold;
}
.admonition.note {
background-color: #f1ebba;
}
.admonition.warning {
background-color: #eed9af;
}
.admonition.danger {
background-color: #e9bcab;
}
/* =================== /* ===================
left navigation left navigation
===================== */ ===================== */

26
docs/theme/docker/static/css/main.less поставляемый
Просмотреть файл

@ -179,7 +179,33 @@ section.header {
border: 2px solid gray; border: 2px solid gray;
} }
.admonition {
padding: 10px;
border: 1px solid grey;
margin-bottom: 10px;
margin-top: 10px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.admonition .admonition-title {
font-weight: bold;
}
.admonition.note {
background-color: rgb(241, 235, 186);
}
.admonition.warning {
background-color: rgb(238, 217, 175);
}
.admonition.danger {
background-color: rgb(233, 188, 171);
}
/* =================== /* ===================
left navigation left navigation

Просмотреть файл

@ -111,6 +111,8 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
} }
func CreateBridgeIface(ifaceName string) error { func CreateBridgeIface(ifaceName string) error {
// FIXME: try more IP ranges
// FIXME: try bigger ranges! /24 is too small.
addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"} addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"}
var ifaceAddr string var ifaceAddr string
@ -127,7 +129,7 @@ func CreateBridgeIface(ifaceName string) error {
} }
} }
if ifaceAddr == "" { if ifaceAddr == "" {
return fmt.Errorf("Impossible to create a bridge. Please create a bridge manually and restart docker with -br <bridgeName>") return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
} else { } else {
Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr) Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
} }

Просмотреть файл

@ -1,38 +0,0 @@
package rcli
import (
"fmt"
"net/http"
"net/url"
"path"
)
// Use this key to encode an RPC call into an URL,
// eg. domain.tld/path/to/method?q=get_user&q=gordon
const ARG_URL_KEY = "q"
func URLToCall(u *url.URL) (method string, args []string) {
return path.Base(u.Path), u.Query()[ARG_URL_KEY]
}
func ListenAndServeHTTP(addr string, service Service) error {
return http.ListenAndServe(addr, http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
cmd, args := URLToCall(r.URL)
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
fmt.Fprintln(w, "Error:", err.Error())
}
}))
}
type AutoFlush struct {
http.ResponseWriter
}
func (w *AutoFlush) Write(data []byte) (int, error) {
ret, err := w.ResponseWriter.Write(data)
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
return ret, err
}

Просмотреть файл

@ -2,6 +2,7 @@ package rcli
import ( import (
"bufio" "bufio"
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -15,22 +16,109 @@ import (
var DEBUG_FLAG bool = false var DEBUG_FLAG bool = false
var CLIENT_SOCKET io.Writer = nil var CLIENT_SOCKET io.Writer = nil
type DockerTCPConn struct {
conn *net.TCPConn
options *DockerConnOptions
optionsBuf *[]byte
handshaked bool
client bool
}
func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn {
return &DockerTCPConn{
conn: conn,
options: &DockerConnOptions{},
client: client,
}
}
func (c *DockerTCPConn) SetOptionRawTerminal() {
c.options.RawTerminal = true
}
func (c *DockerTCPConn) GetOptions() *DockerConnOptions {
if c.client && !c.handshaked {
// Attempt to parse options encoded as a JSON dict and store
// the reminder of what we read from the socket in a buffer.
//
// bufio (and its ReadBytes method) would have been nice here,
// but if json.Unmarshal() fails (which will happen if we speak
// to a version of docker that doesn't send any option), then
// we can't put the data back in it for the next Read().
c.handshaked = true
buf := make([]byte, 4096)
if n, _ := c.conn.Read(buf); n > 0 {
buf = buf[:n]
if nl := bytes.IndexByte(buf, '\n'); nl != -1 {
if err := json.Unmarshal(buf[:nl], c.options); err == nil {
buf = buf[nl+1:]
}
}
c.optionsBuf = &buf
}
}
return c.options
}
func (c *DockerTCPConn) Read(b []byte) (int, error) {
if c.optionsBuf != nil {
// Consume what we buffered in GetOptions() first:
optionsBuf := *c.optionsBuf
optionsBuflen := len(optionsBuf)
copied := copy(b, optionsBuf)
if copied < optionsBuflen {
optionsBuf = optionsBuf[copied:]
c.optionsBuf = &optionsBuf
return copied, nil
}
c.optionsBuf = nil
return copied, nil
}
return c.conn.Read(b)
}
func (c *DockerTCPConn) Write(b []byte) (int, error) {
optionsLen := 0
if !c.client && !c.handshaked {
c.handshaked = true
options, _ := json.Marshal(c.options)
options = append(options, '\n')
if optionsLen, err := c.conn.Write(options); err != nil {
return optionsLen, err
}
}
n, err := c.conn.Write(b)
return n + optionsLen, err
}
func (c *DockerTCPConn) Flush() error {
_, err := c.Write([]byte{})
return err
}
func (c *DockerTCPConn) Close() error { return c.conn.Close() }
func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
// Connect to a remote endpoint using protocol `proto` and address `addr`, // Connect to a remote endpoint using protocol `proto` and address `addr`,
// issue a single call, and return the result. // issue a single call, and return the result.
// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols. // `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
func Call(proto, addr string, args ...string) (*net.TCPConn, error) { func Call(proto, addr string, args ...string) (DockerConn, error) {
cmd, err := json.Marshal(args) cmd, err := json.Marshal(args)
if err != nil { if err != nil {
return nil, err return nil, err
} }
conn, err := net.Dial(proto, addr) conn, err := dialDocker(proto, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil { if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
return nil, err return nil, err
} }
return conn.(*net.TCPConn), nil return conn, nil
} }
// Listen on `addr`, using protocol `proto`, for incoming rcli calls, // Listen on `addr`, using protocol `proto`, for incoming rcli calls,
@ -46,6 +134,10 @@ func ListenAndServe(proto, addr string, service Service) error {
if conn, err := listener.Accept(); err != nil { if conn, err := listener.Accept(); err != nil {
return err return err
} else { } else {
conn, err := newDockerServerConn(conn)
if err != nil {
return err
}
go func() { go func() {
if DEBUG_FLAG { if DEBUG_FLAG {
CLIENT_SOCKET = conn CLIENT_SOCKET = conn
@ -63,7 +155,7 @@ func ListenAndServe(proto, addr string, service Service) error {
// Parse an rcli call on a new connection, and pass it to `service` if it // Parse an rcli call on a new connection, and pass it to `service` if it
// is valid. // is valid.
func Serve(conn io.ReadWriter, service Service) error { func Serve(conn DockerConn, service Service) error {
r := bufio.NewReader(conn) r := bufio.NewReader(conn)
var args []string var args []string
if line, err := r.ReadString('\n'); err != nil { if line, err := r.ReadString('\n'); err != nil {

Просмотреть файл

@ -8,15 +8,99 @@ package rcli
// are the usual suspects. // are the usual suspects.
import ( import (
"errors"
"flag" "flag"
"fmt" "fmt"
"github.com/dotcloud/docker/term"
"io" "io"
"log" "log"
"net"
"os"
"reflect" "reflect"
"strings" "strings"
) )
type DockerConnOptions struct {
RawTerminal bool
}
type DockerConn interface {
io.ReadWriteCloser
CloseWrite() error
CloseRead() error
GetOptions() *DockerConnOptions
SetOptionRawTerminal()
Flush() error
}
type DockerLocalConn struct {
writer io.WriteCloser
savedState *term.State
}
func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
return &DockerLocalConn{
writer: w,
}
}
func (c *DockerLocalConn) Read(b []byte) (int, error) {
return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
}
func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
func (c *DockerLocalConn) Close() error {
if c.savedState != nil {
RestoreTerminal(c.savedState)
c.savedState = nil
}
return c.writer.Close()
}
func (c *DockerLocalConn) Flush() error { return nil }
func (c *DockerLocalConn) CloseWrite() error { return nil }
func (c *DockerLocalConn) CloseRead() error { return nil }
func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
func (c *DockerLocalConn) SetOptionRawTerminal() {
if state, err := SetRawTerminal(); err != nil {
if os.Getenv("DEBUG") != "" {
log.Printf("Can't set the terminal in raw mode: %s", err)
}
} else {
c.savedState = state
}
}
var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment")
func dialDocker(proto string, addr string) (DockerConn, error) {
conn, err := net.Dial(proto, addr)
if err != nil {
return nil, err
}
switch i := conn.(type) {
case *net.TCPConn:
return NewDockerTCPConn(i, true), nil
}
return nil, UnknownDockerProto
}
func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
switch i := conn.(type) {
case *net.TCPConn:
return NewDockerTCPConn(i, client), nil
}
return nil, UnknownDockerProto
}
func newDockerServerConn(conn net.Conn) (DockerConn, error) {
return newDockerFromConn(conn, false)
}
type Service interface { type Service interface {
Name() string Name() string
Help() string Help() string
@ -26,11 +110,11 @@ type Cmd func(io.ReadCloser, io.Writer, ...string) error
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
// FIXME: For reverse compatibility // FIXME: For reverse compatibility
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
return LocalCall(service, stdin, stdout, args...) return LocalCall(service, stdin, stdout, args...)
} }
func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
if len(args) == 0 { if len(args) == 0 {
args = []string{"help"} args = []string{"help"}
} }
@ -49,7 +133,7 @@ func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...s
if method != nil { if method != nil {
return method(stdin, stdout, flags.Args()[1:]...) return method(stdin, stdout, flags.Args()[1:]...)
} }
return errors.New("No such command: " + cmd) return fmt.Errorf("No such command: %s", cmd)
} }
func getMethod(service Service, name string) Cmd { func getMethod(service Service, name string) Cmd {
@ -59,7 +143,7 @@ func getMethod(service Service, name string) Cmd {
stdout.Write([]byte(service.Help())) stdout.Write([]byte(service.Help()))
} else { } else {
if method := getMethod(service, args[0]); method == nil { if method := getMethod(service, args[0]); method == nil {
return errors.New("No such command: " + args[0]) return fmt.Errorf("No such command: %s", args[0])
} else { } else {
method(stdin, stdout, "--help") method(stdin, stdout, "--help")
} }

27
rcli/utils.go Normal file
Просмотреть файл

@ -0,0 +1,27 @@
package rcli
import (
"github.com/dotcloud/docker/term"
"os"
"os/signal"
)
//FIXME: move these function to utils.go (in rcli to avoid import loop)
func SetRawTerminal() (*term.State, error) {
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return nil, err
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
term.Restore(int(os.Stdin.Fd()), oldState)
os.Exit(0)
}()
return oldState, err
}
func RestoreTerminal(state *term.State) {
term.Restore(int(os.Stdin.Fd()), state)
}

Просмотреть файл

@ -116,7 +116,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
if err := container.FromDisk(); err != nil { if err := container.FromDisk(); err != nil {
return nil, err return nil, err
} }
container.State.initLock()
if container.Id != id { if container.Id != id {
return container, fmt.Errorf("Container %s is stored at %s", container.Id, id) return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
} }
@ -136,6 +135,7 @@ func (runtime *Runtime) Register(container *Container) error {
} }
// FIXME: if the container is supposed to be running but is not, auto restart it? // FIXME: if the container is supposed to be running but is not, auto restart it?
// if so, then we need to restart monitor and init a new lock
// If the container is supposed to be running, make sure of it // If the container is supposed to be running, make sure of it
if container.State.Running { if container.State.Running {
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil { if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
@ -150,10 +150,10 @@ func (runtime *Runtime) Register(container *Container) error {
} }
} }
} }
container.State.initLock()
container.runtime = runtime container.runtime = runtime
// Setup state lock (formerly in newState()
container.State.initLock()
// Attach to stdout and stderr // Attach to stdout and stderr
container.stderr = newWriteBroadcaster() container.stderr = newWriteBroadcaster()
container.stdout = newWriteBroadcaster() container.stdout = newWriteBroadcaster()

Просмотреть файл

@ -1,6 +1,7 @@
package docker package docker
import ( import (
"github.com/dotcloud/docker/rcli"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -77,7 +78,7 @@ func init() {
runtime: runtime, runtime: runtime,
} }
// Retrieve the Image // Retrieve the Image
if err := srv.CmdPull(os.Stdin, os.Stdout, unitTestImageName); err != nil { if err := srv.CmdPull(os.Stdin, rcli.NewDockerLocalConn(os.Stdout), unitTestImageName); err != nil {
panic(err) panic(err)
} }
} }
@ -314,7 +315,7 @@ func TestRestore(t *testing.T) {
// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running' // Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
cStdin, _ := container2.StdinPipe() cStdin, _ := container2.StdinPipe()
cStdin.Close() cStdin.Close()
if err := container2.WaitTimeout(time.Second); err != nil { if err := container2.WaitTimeout(2 * time.Second); err != nil {
t.Fatal(err) t.Fatal(err)
} }
container2.State.Running = true container2.State.Running = true
@ -358,4 +359,5 @@ func TestRestore(t *testing.T) {
if err := container3.Run(); err != nil { if err := container3.Run(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
container2.State.Running = false
} }

Просмотреть файл

@ -11,9 +11,7 @@ type State struct {
Pid int Pid int
ExitCode int ExitCode int
StartedAt time.Time StartedAt time.Time
l *sync.Mutex
stateChangeLock *sync.Mutex
stateChangeCond *sync.Cond
} }
// String returns a human-readable description of the state // String returns a human-readable description of the state
@ -29,31 +27,22 @@ func (s *State) setRunning(pid int) {
s.ExitCode = 0 s.ExitCode = 0
s.Pid = pid s.Pid = pid
s.StartedAt = time.Now() s.StartedAt = time.Now()
s.broadcast()
} }
func (s *State) setStopped(exitCode int) { func (s *State) setStopped(exitCode int) {
s.Running = false s.Running = false
s.Pid = 0 s.Pid = 0
s.ExitCode = exitCode s.ExitCode = exitCode
s.broadcast()
} }
func (s *State) initLock() { func (s *State) initLock() {
if s.stateChangeLock == nil { s.l = &sync.Mutex{}
s.stateChangeLock = &sync.Mutex{}
s.stateChangeCond = sync.NewCond(s.stateChangeLock)
}
} }
func (s *State) broadcast() { func (s *State) lock() {
s.stateChangeLock.Lock() s.l.Lock()
s.stateChangeCond.Broadcast()
s.stateChangeLock.Unlock()
} }
func (s *State) wait() { func (s *State) unlock() {
s.stateChangeLock.Lock() s.l.Unlock()
s.stateChangeCond.Wait()
s.stateChangeLock.Unlock()
} }

Просмотреть файл

@ -15,7 +15,8 @@ void MakeRaw(int fd) {
ioctl(fd, TCGETS, &t); ioctl(fd, TCGETS, &t);
t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN); t.c_oflag &= ~OPOST;
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
t.c_cflag &= ~(CSIZE | PARENB); t.c_cflag &= ~(CSIZE | PARENB);
t.c_cflag |= CS8; t.c_cflag |= CS8;

Просмотреть файл

@ -341,3 +341,46 @@ func TruncateId(id string) string {
} }
return id[:shortLen] return id[:shortLen]
} }
// Code c/c from io.Copy() modified to handle escape sequence
func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
buf := make([]byte, 32*1024)
for {
nr, er := src.Read(buf)
if nr > 0 {
// ---- Docker addition
// char 16 is C-p
if nr == 1 && buf[0] == 16 {
nr, er = src.Read(buf)
// char 17 is C-q
if nr == 1 && buf[0] == 17 {
if err := src.Close(); err != nil {
return 0, err
}
return 0, io.EOF
}
}
// ---- End of docker
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er == io.EOF {
break
}
if er != nil {
err = er
break
}
}
return written, err
}