Compare commits

..

64 Commits

Author SHA1 Message Date
Solomon Hykes 40ebe78bb1 Bumped version to 0.1.4 2013-04-09 13:00:50 -07:00
Solomon Hykes 1b7115a337 Merge remote-tracking branch 'origin/disable_signals-create_escape_sequence' 2013-04-09 12:56:32 -07:00
Guillaume J. Charmes 2e6a5bc7ee Update README with escape sequence 2013-04-09 12:55:26 -07:00
Guillaume J. Charmes 72cef46e5e Fix merge issue 2013-04-09 12:55:26 -07:00
Guillaume J. Charmes 626bfd87a7 Use integers instead of non-printable chars in the escape sequence detection 2013-04-09 12:55:26 -07:00
Guillaume J. Charmes 8f41f1fa60 Remove unused optimization that could lead in loosing the escape sequence 2013-04-09 12:55:26 -07:00
Guillaume J. Charmes faa8843650 Look for the escape sequence only in tty mode 2013-04-09 12:55:17 -07:00
Guillaume J. Charmes 0d9e54367f Fix deadlock on stop failure 2013-04-09 12:06:01 -07:00
Guillaume J. Charmes 1f70b1e15d Implement an escape sequence in order to be able to detach from a container 2013-04-09 11:06:17 -07:00
Guillaume J. Charmes 3f63b87807 Disable signal catching and enable real posix raw mode 2013-04-09 11:06:17 -07:00
Solomon Hykes 9c3d2b6a4e Merge branch 'master' of ssh://github.com/dotcloud/docker 2013-04-09 11:02:43 -07:00
Solomon Hykes 1716fccbcc Merge remote-tracking branch 'origin/change_run_detach_behavious_tty_mode' 2013-04-09 10:39:13 -07:00
Solomon Hykes 9043e4c757 Merge pull request #363 from dhrp/docs
Added code and color for 'note' and updated the examples note.
2013-04-09 10:36:44 -07:00
Solomon Hykes 2e9a73c5d8 Merge remote-tracking branch 'origin/fix_flush_behaviour' 2013-04-09 10:31:56 -07:00
Guillaume J. Charmes 1eaaa6b744 Flush stdout on import to avoid deadklock when waiting for stdin (import -). Fixed #365 2013-04-09 10:02:57 -07:00
Guillaume J. Charmes cb54e9c659 Flush whether or not there we set the rawmode to avoid the client to lock 2013-04-09 09:59:30 -07:00
Guillaume J. Charmes 7c2b085d1a Add inconditionnal lock in Start/Stop/Kill to avoid races 2013-04-09 09:09:54 -07:00
Guillaume J. Charmes d063d52cce Update the unit test to reflect the new CmdRun behaviour in tty mode 2013-04-09 08:18:36 -07:00
Guillaume J. Charmes 64c1b6d9cd Change the behaviour of CmdRun in tty mode: dont kill the process uppon detach 2013-04-09 08:18:16 -07:00
Guillaume J. Charmes 329f4449dc Remove the mutexes and use chan instead in order to handle the wait lock 2013-04-09 07:57:59 -07:00
Solomon Hykes 0767916ade Merge pull request #346 from srid/patch-2
make the service example work
2013-04-08 22:12:34 -07:00
Solomon Hykes 10923c7890 Merge remote-tracking branch 'origin/pty_fix-1' 2013-04-08 21:12:22 -07:00
Thatcher Peskens 2832ea0cfe Added code and color for 'note' and updated the hello world note. 2013-04-08 20:10:47 -07:00
Solomon Hykes a7299a3f26 Merge remote-tracking branch 'origin/unit_test_improvment-2' 2013-04-08 18:29:12 -07:00
Solomon Hykes 1601366cb6 Make it more clear when Docker fails to allocate a free IP range for its bridge 2013-04-08 18:16:58 -07:00
Louis Opter e9a68801ba Update the tests according to the "optional raw mode" changes 2013-04-08 16:07:12 -07:00
Guillaume J. Charmes f73401fb9a Add missing file 2013-04-08 16:07:12 -07:00
Guillaume J. Charmes dcf4572a69 Set the raw mode only for tty enabled containers 2013-04-08 16:07:12 -07:00
Guillaume J. Charmes d530d581f7 Make commands.go more idiomatic. Use DockerConn only when needed, keep io.Writer when not 2013-04-08 15:58:09 -07:00
Guillaume J. Charmes bdf05d8368 Reenable CmdRunAttachStdin and CmdRunHostname now using the DockConn interface 2013-04-08 15:58:09 -07:00
Guillaume J. Charmes b71b226cc1 Improve error management (avoid unwanted output in tests) 2013-04-08 15:58:09 -07:00
Guillaume J. Charmes 80f6b4587b Edit the tests for them to use the new command API. Disable TestRunHostname and TestAttachStdin. 2013-04-08 15:58:09 -07:00
Guillaume J. Charmes e6e9c1cd62 Use io.WriteCloser instead of *os.File in DockerLocalConn so we can use it with standard writers and pipes 2013-04-08 15:58:09 -07:00
Guillaume J. Charmes 246eed52de Move DockerLocalConn and terminal functions form package "main" to "rcli" in order to be able to use DockerLocalConn in commands_test.go 2013-04-08 15:58:09 -07:00
Louis Opter b306a60738 Simplification in the goroutine that restore the terminal state on SIGINT 2013-04-08 15:58:09 -07:00
Louis Opter 7d0ab3858e Only set the terminal in raw mode for commands which need it
The raw mode is actually only needed when you attach to a container.
Having it enabled all the time can be a pain, e.g: if docker crashes
your terminal will end up in a broken state.

Since we are currently missing a real API for the docker daemon to
negotiate this kind of options, this changeset actually enable the raw
mode on the login (because it outputs a password), run and attach
commands.

This "optional raw mode" is implemented by passing a more complicated
interface than io.Writer as the stdout argument of each command. This
interface (DockerConn) exposes a method which allows the command to set
the terminal in raw mode or not.

Finally, the code added by this changeset will be deprecated by a real
API for the docker daemon.
2013-04-08 15:58:09 -07:00
Louis Opter 4e5001b46a Remove the unused http transport from rcli 2013-04-08 15:58:09 -07:00
Solomon Hykes b8f9803459 Merge pull request #347 from kencochrane/303_docs_fix
improved the example docs to help #303
2013-04-08 11:50:07 -07:00
Solomon Hykes 0c018d3697 Merge pull request #356 from flavio/improve_python_web_app_example
Extend the documentation covering the web app example
2013-04-08 11:49:17 -07:00
Flavio Castelli 72fdb41069 Extend the documentation covering the web app example
Make it clear how to access the web app running inside of the container
from the host.
2013-04-08 17:39:30 +02:00
Ken Cochrane 6eb8a74ff9 added headers to examples linking back to running the examples page 2013-04-07 10:23:00 -04:00
Ken Cochrane 81ebf4fcf6 made a new running the examples page, and added a link to the top of each example to the page to show people how to run them. 2013-04-07 10:21:08 -04:00
Sridhar Ratnakumar 9875a9b1f1 sync with README 2013-04-07 00:43:57 -07:00
Sridhar Ratnakumar 27feba4594 make the service example work
issue #98 requires connecting to localhost (which `hostname` may resolve to) will not work.
2013-04-07 00:41:24 -07:00
Guillaume J. Charmes c83393a541 Move the DockerConn flush to its own function 2013-04-05 20:08:31 -07:00
Guillaume J. Charmes 7e1e7d14fa Make sure to flush buffer when setting raw mode 2013-04-05 19:48:49 -07:00
Guillaume J. Charmes 99b5bec069 Fix run disconnect behavious in tty mode + add unit test to enforce it 2013-04-05 19:02:35 -07:00
Guillaume J. Charmes 7d8895545e Cleanup pty variable names 2013-04-05 17:40:55 -07:00
Guillaume J. Charmes 33a5fe3bd4 Make sure the process start in his own session and grabs the terminal 2013-04-05 17:40:55 -07:00
Guillaume J. Charmes 847a8f45a4 Merge the 3 ptys in 1 2013-04-05 17:40:55 -07:00
Solomon Hykes 8cf30395a1 Changed default bridge interface do 'docker0' 2013-04-05 14:16:19 -07:00
Solomon Hykes 22adb52c0a The flag to use a pre-existing bridge interface is '-b'. Added explanation 2013-04-05 14:16:04 -07:00
Solomon Hykes 793c1ad990 Merge remote-tracking branch 'origin/219-default-bridge-2' 2013-04-05 14:02:16 -07:00
Solomon Hykes febaeebfb8 Add tests of tcp port allocator 2013-04-05 13:03:24 -07:00
Solomon Hykes d32f184696 Fix a race condition when running the port allocator 2013-04-05 13:03:04 -07:00
Guillaume J. Charmes 20085794f0 Increase the timeout in TestStart() to make sure the container has the time to die within the function 2013-04-05 02:01:38 -07:00
Guillaume J. Charmes b76b329ef0 Prevent destroy() to stop twice container in TestRestore() 2013-04-04 20:40:42 -07:00
Guillaume J. Charmes bae6f95830 Increase the timeout of TestRestore() to avoid unwanted timeout error 2013-04-04 20:32:44 -07:00
Guillaume J. Charmes cda9cf1539 Avoid unwanted warnings from destroy() in TestStart() 2013-04-04 20:30:24 -07:00
Guillaume J. Charmes 1b370f9d8d Move the default bridge name to a constant 2013-04-04 05:33:28 -07:00
Guillaume J. Charmes aa4bf4284b If bridge does not exists, try to create it 2013-04-03 16:17:03 -07:00
Guillaume J. Charmes d9a9bfc9c7 Make LXC aware of custom bridge 2013-04-03 16:15:44 -07:00
Guillaume J. Charmes 90a6e310fe Add an helper function to check if two network overlaps. Also add unit tests for this function 2013-04-03 16:15:43 -07:00
Guillaume J. Charmes f39af7e05d Put the bridge interface name in the command line 2013-04-03 16:15:43 -07:00
29 changed files with 777 additions and 200 deletions
+9 -1
View File
@@ -134,6 +134,12 @@ docker pull base
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
--------------------------------------
@@ -183,7 +189,9 @@ JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
PORT=$(docker port $JOB 4444)
# 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
echo "Daemon received: $(docker logs $JOB)"
+21 -6
View File
@@ -18,7 +18,7 @@ import (
"unicode"
)
const VERSION = "0.1.3"
const VERSION = "0.1.4"
var GIT_COMMIT string
@@ -62,7 +62,7 @@ func (srv *Server) Help() string {
}
// '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
// sequences and echo.
//
@@ -113,6 +113,8 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
return readStringOnRawTerminal(stdin, stdout, false)
}
stdout.SetOptionRawTerminal()
cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
if err := cmd.Parse(args); err != nil {
return nil
@@ -417,7 +419,8 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
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")
var archive io.Reader
var resp *http.Response
@@ -464,7 +467,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
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")
if err := cmd.Parse(args); err != 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))
}
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")
if err := cmd.Parse(args); err != 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)
}
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)
}
@@ -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)
}
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)
if err != nil {
return err
@@ -884,6 +892,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
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
container, err := srv.runtime.Create(config)
if err != nil {
+89 -21
View File
@@ -2,8 +2,8 @@ package docker
import (
"bufio"
"bytes"
"fmt"
"github.com/dotcloud/docker/rcli"
"io"
"io/ioutil"
"strings"
@@ -69,15 +69,27 @@ func TestRunHostname(t *testing.T) {
srv := &Server{runtime: runtime}
var stdin, stdout bytes.Buffer
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
if err := srv.CmdRun(ioutil.NopCloser(&stdin), &nopWriteCloser{&stdout}, "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
stdin, _ := io.Pipe()
stdout, stdoutPipe := io.Pipe()
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)
}
})
if output := string(stdout.Bytes()); output != "foobar\n" {
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", output)
close(c)
}()
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) {
@@ -93,7 +105,7 @@ func TestRunExit(t *testing.T) {
stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{})
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)
}()
@@ -147,7 +159,7 @@ func TestRunDisconnect(t *testing.T) {
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, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
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.
// 'docker run -i -a stdin' should sends the client's stdin to the command,
// then detach from it and print the container id.
func TestAttachStdin(t *testing.T) {
func TestRunAttachStdin(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@@ -190,31 +248,41 @@ func TestAttachStdin(t *testing.T) {
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdinR, stdinW := io.Pipe()
var stdout bytes.Buffer
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
ch := make(chan struct{})
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)
}()
// Send input to the command, close stdin, wait for CmdRun to return
setTimeout(t, "Read/Write timed out", 2*time.Second, func() {
if _, err := stdinW.Write([]byte("hi there\n")); err != nil {
// Send input to the command, close stdin
setTimeout(t, "Write timed out", 2*time.Second, func() {
if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
t.Fatal(err)
}
if err := stdinPipe.Close(); err != nil {
t.Fatal(err)
}
stdinW.Close()
<-ch
})
// Check output
cmdOutput := string(stdout.Bytes())
container := runtime.List()[0]
// Check output
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != container.ShortId()+"\n" {
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() {
container.Wait()
})
@@ -270,7 +338,7 @@ func TestAttachDisconnect(t *testing.T) {
go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdAttach returns.
srv.CmdAttach(stdin, stdoutPipe, container.Id)
srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
close(c1)
}()
+41 -63
View File
@@ -40,11 +40,11 @@ type Container struct {
stdin io.ReadCloser
stdinPipe io.WriteCloser
ptyStdinMaster io.Closer
ptyStdoutMaster io.Closer
ptyStderrMaster io.Closer
ptyMaster io.Closer
runtime *Runtime
waitLock chan struct{}
}
type Config struct {
@@ -135,6 +135,7 @@ type NetworkSettings struct {
IpAddress string
IpPrefixLen int
Gateway string
Bridge string
PortMapping map[string]string
}
@@ -179,63 +180,37 @@ func (container *Container) generateLXCConfig() error {
}
func (container *Container) startPty() error {
stdoutMaster, stdoutSlave, err := pty.Open()
ptyMaster, ptySlave, err := pty.Open()
if err != nil {
return err
}
container.ptyStdoutMaster = stdoutMaster
container.cmd.Stdout = stdoutSlave
stderrMaster, stderrSlave, err := pty.Open()
if err != nil {
return err
}
container.ptyStderrMaster = stderrMaster
container.cmd.Stderr = stderrSlave
container.ptyMaster = ptyMaster
container.cmd.Stdout = ptySlave
container.cmd.Stderr = ptySlave
// Copy the PTYs to our broadcasters
go func() {
defer container.stdout.CloseWriters()
Debugf("[startPty] Begin of stdout pipe")
io.Copy(container.stdout, stdoutMaster)
io.Copy(container.stdout, ptyMaster)
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
var stdinSlave io.ReadCloser
if container.Config.OpenStdin {
var stdinMaster io.WriteCloser
stdinMaster, stdinSlave, err = pty.Open()
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}
container.cmd.Stdin = ptySlave
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
go func() {
defer container.stdin.Close()
Debugf("[startPty] Begin of stdin pipe")
io.Copy(stdinMaster, container.stdin)
io.Copy(ptyMaster, container.stdin)
Debugf("[startPty] End of stdin pipe")
}()
}
if err := container.cmd.Start(); err != nil {
return err
}
stdoutSlave.Close()
stderrSlave.Close()
if stdinSlave != nil {
stdinSlave.Close()
}
ptySlave.Close()
return nil
}
@@ -277,10 +252,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
if cStderr != nil {
defer cStderr.Close()
}
if container.Config.StdinOnce {
if container.Config.StdinOnce && !container.Config.Tty {
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 {
Debugf("[error] attach stdin: %s\n", err)
}
@@ -364,6 +343,9 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
}
func (container *Container) Start() error {
container.State.lock()
defer container.State.unlock()
if container.State.Running {
return fmt.Errorf("The container %s is already running.", container.Id)
}
@@ -430,6 +412,9 @@ func (container *Container) Start() error {
// FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we can restore after crash etc.
container.State.setRunning(container.cmd.Process.Pid)
// Init the lock
container.waitLock = make(chan struct{})
container.ToDisk()
go container.monitor()
return nil
@@ -491,6 +476,7 @@ func (container *Container) allocateNetwork() error {
}
}
container.network = iface
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
container.NetworkSettings.Gateway = iface.Gateway.String()
@@ -528,19 +514,9 @@ func (container *Container) monitor() {
Debugf("%s: Error close stderr: %s", container.Id, err)
}
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)
if container.ptyMaster != nil {
if err := container.ptyMaster.Close(); err != nil {
Debugf("%s: Error closing Pty master: %s", container.Id, err)
}
}
@@ -555,6 +531,10 @@ func (container *Container) monitor() {
// Report status back
container.State.setStopped(exitCode)
// Release the lock
close(container.waitLock)
if err := container.ToDisk(); err != nil {
// 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
@@ -567,7 +547,7 @@ func (container *Container) monitor() {
}
func (container *Container) kill() error {
if container.cmd == nil {
if !container.State.Running || container.cmd == nil {
return nil
}
if err := container.cmd.Process.Kill(); err != nil {
@@ -579,13 +559,14 @@ func (container *Container) kill() error {
}
func (container *Container) Kill() error {
if !container.State.Running {
return nil
}
container.State.lock()
defer container.State.unlock()
return container.kill()
}
func (container *Container) Stop() error {
container.State.lock()
defer container.State.unlock()
if !container.State.Running {
return nil
}
@@ -594,7 +575,7 @@ func (container *Container) Stop() error {
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
log.Print(string(output))
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
}
}
@@ -602,7 +583,7 @@ func (container *Container) Stop() error {
// 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 {
if err := container.kill(); err != nil {
return err
}
}
@@ -621,10 +602,7 @@ func (container *Container) Restart() error {
// Wait blocks until the container stops running, then returns its exit code.
func (container *Container) Wait() int {
for container.State.Running {
container.State.wait()
}
<-container.waitLock
return container.State.ExitCode
}
+1
View File
@@ -267,6 +267,7 @@ func TestStart(t *testing.T) {
// Try to avoid the timeoout in destroy. Best effort, don't check error
cStdin, _ := container.StdinPipe()
cStdin.Close()
container.WaitTimeout(2 * time.Second)
}
func TestRun(t *testing.T) {
+19 -23
View File
@@ -8,7 +8,6 @@ import (
"io"
"log"
"os"
"os/signal"
)
var GIT_COMMIT string
@@ -22,7 +21,13 @@ func main() {
// FIXME: Switch d and D ? (to be more sshd like)
flDaemon := flag.Bool("d", false, "Daemon mode")
flDebug := flag.Bool("D", false, "Debug mode")
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
flag.Parse()
if *bridgeName != "" {
docker.NetworkBridgeIface = *bridgeName
} else {
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
}
if *flDebug {
os.Setenv("DEBUG", "1")
}
@@ -51,29 +56,21 @@ func daemon() 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
// CloseWrite(), which we need to cleanly signal that stdin is closed without
// closing the connection.
// 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 {
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 {
_, err := io.Copy(os.Stdout, conn)
return err
@@ -98,12 +95,11 @@ func runCommand(args []string) error {
if err != nil {
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
}
}
if oldState != nil {
term.Restore(int(os.Stdin.Fd()), oldState)
}
return nil
}
+33 -1
View File
@@ -39,4 +39,36 @@ Notes
* The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
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
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.
+2 -1
View File
@@ -69,7 +69,8 @@ Expose a service on a TCP port
# 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.
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
echo "Daemon received: $(docker logs $JOB)"
+4
View File
@@ -0,0 +1,4 @@
.. note::
This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`
+3 -1
View File
@@ -6,8 +6,10 @@
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
+4 -1
View File
@@ -6,6 +6,9 @@
Hello World Daemon
==================
.. include:: example_header.inc
The most boring daemon ever written.
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")
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.
- **"base"** is the image we want to run the command inside of.
+1
View File
@@ -12,6 +12,7 @@ Contents:
.. toctree::
:maxdepth: 1
running_examples
hello_world
hello_world_daemon
python_web_app
+20
View File
@@ -6,6 +6,9 @@
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.
**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)
- **"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.
.. 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.
.. 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:**
@@ -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
============================
.. include:: example_header.inc
**Video:**
+22 -1
View File
@@ -82,7 +82,7 @@ h4 {
.btn-custom {
background-color: #292929 !important;
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: -moz-linear-gradient(top, #515151, #282828);
background-image: -ms-linear-gradient(top, #515151, #282828);
@@ -131,6 +131,27 @@ section.header {
margin: 15px 15px 15px 0;
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
===================== */
+26
View File
@@ -179,7 +179,33 @@ section.header {
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
+1 -1
View File
@@ -16,7 +16,7 @@ lxc.utsname = {{.Id}}
# network configuration
lxc.network.type = veth
lxc.network.flags = up
lxc.network.link = lxcbr0
lxc.network.link = {{.NetworkSettings.Bridge}}
lxc.network.name = eth0
lxc.network.mtu = 1500
lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}}
+102 -8
View File
@@ -12,10 +12,12 @@ import (
"sync"
)
var NetworkBridgeIface string
const (
networkBridgeIface = "lxcbr0"
portRangeStart = 49153
portRangeEnd = 65535
DefaultNetworkBridge = "docker0"
portRangeStart = 49153
portRangeEnd = 65535
)
// Calculates the first and last IP addresses in an IPNet
@@ -29,6 +31,19 @@ func networkRange(network *net.IPNet) (net.IP, net.IP) {
return firstIP, lastIP
}
// Detects overlap between one IPNet and another
func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
firstIP, _ := networkRange(netX)
if netY.Contains(firstIP) {
return true
}
firstIP, _ = networkRange(netY)
if netX.Contains(firstIP) {
return true
}
return false
}
// Converts a 4 bytes IP into a 32 bit integer
func ipToInt(ip net.IP) int32 {
return int32(binary.BigEndian.Uint32(ip.To4()))
@@ -51,6 +66,19 @@ func networkSize(mask net.IPMask) int32 {
return int32(binary.BigEndian.Uint32(m)) + 1
}
//Wrapper around the ip command
func ip(args ...string) (string, error) {
path, err := exec.LookPath("ip")
if err != nil {
return "", fmt.Errorf("command not found: ip")
}
output, err := exec.Command(path, args...).CombinedOutput()
if err != nil {
return "", fmt.Errorf("ip failed: ip %v", strings.Join(args, " "))
}
return string(output), nil
}
// Wrapper around the iptables command
func iptables(args ...string) error {
path, err := exec.LookPath("iptables")
@@ -63,6 +91,66 @@ func iptables(args ...string) error {
return nil
}
func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
output, err := ip("route")
if err != nil {
return err
}
Debugf("Routes:\n\n%s", output)
for _, line := range strings.Split(output, "\n") {
if strings.Trim(line, "\r\n\t ") == "" || strings.Contains(line, "default") {
continue
}
if _, network, err := net.ParseCIDR(strings.Split(line, " ")[0]); err != nil {
return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line)
} else if networkOverlaps(dockerNetwork, network) {
return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork.String(), line)
}
}
return nil
}
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"}
var ifaceAddr string
for _, addr := range addrs {
_, dockerNetwork, err := net.ParseCIDR(addr)
if err != nil {
return err
}
if err := checkRouteOverlaps(dockerNetwork); err == nil {
ifaceAddr = addr
break
} else {
Debugf("%s: %s", addr, err)
}
}
if ifaceAddr == "" {
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 {
Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
}
if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output)
}
if output, err := ip("addr", "add", ifaceAddr, "dev", ifaceName); err != nil {
return fmt.Errorf("Unable to add private network: %s (%s)", err, output)
}
if output, err := ip("link", "set", ifaceName, "up"); err != nil {
return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output)
}
if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
}
return nil
}
// Return the IPv4 address of a network interface
func getIfaceAddr(name string) (net.Addr, error) {
iface, err := net.InterfaceByName(name)
@@ -167,10 +255,6 @@ type PortAllocator struct {
}
func (alloc *PortAllocator) runFountain() {
if alloc.fountain != nil {
return
}
alloc.fountain = make(chan int)
for {
for port := portRangeStart; port < portRangeEnd; port++ {
alloc.fountain <- port
@@ -180,6 +264,7 @@ func (alloc *PortAllocator) runFountain() {
// FIXME: Release can no longer fail, change its prototype to reflect that.
func (alloc *PortAllocator) Release(port int) error {
Debugf("Releasing %d", port)
alloc.lock.Lock()
delete(alloc.inUse, port)
alloc.lock.Unlock()
@@ -187,6 +272,7 @@ func (alloc *PortAllocator) Release(port int) error {
}
func (alloc *PortAllocator) Acquire(port int) (int, error) {
Debugf("Acquiring %d", port)
if port == 0 {
// Allocate a port from the fountain
for port := range alloc.fountain {
@@ -208,6 +294,7 @@ func (alloc *PortAllocator) Acquire(port int) (int, error) {
func newPortAllocator() (*PortAllocator, error) {
allocator := &PortAllocator{
inUse: make(map[int]struct{}),
fountain: make(chan int),
}
go allocator.runFountain()
return allocator, nil
@@ -407,7 +494,14 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
addr, err := getIfaceAddr(bridgeIface)
if err != nil {
return nil, fmt.Errorf("Couldn't find bridge interface %s (%s).\nPlease create it with 'ip link add lxcbr0 type bridge; ip addr add ADDRESS/MASK dev lxcbr0'", bridgeIface, err)
// If the iface is not found, try to create it
if err := CreateBridgeIface(bridgeIface); err != nil {
return nil, err
}
addr, err = getIfaceAddr(bridgeIface)
if err != nil {
return nil, err
}
}
network := addr.(*net.IPNet)
+71
View File
@@ -18,6 +18,42 @@ func TestIptables(t *testing.T) {
}
}
func TestPortAllocation(t *testing.T) {
allocator, err := newPortAllocator()
if err != nil {
t.Fatal(err)
}
if port, err := allocator.Acquire(80); err != nil {
t.Fatal(err)
} else if port != 80 {
t.Fatalf("Acquire(80) should return 80, not %d", port)
}
port, err := allocator.Acquire(0)
if err != nil {
t.Fatal(err)
}
if port <= 0 {
t.Fatalf("Acquire(0) should return a non-zero port")
}
if _, err := allocator.Acquire(port); err == nil {
t.Fatalf("Acquiring a port already in use should return an error")
}
if newPort, err := allocator.Acquire(0); err != nil {
t.Fatal(err)
} else if newPort == port {
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
}
if _, err := allocator.Acquire(80); err == nil {
t.Fatalf("Acquiring a port already in use should return an error")
}
if err := allocator.Release(80); err != nil {
t.Fatal(err)
}
if _, err := allocator.Acquire(80); err != nil {
t.Fatal(err)
}
}
func TestNetworkRange(t *testing.T) {
// Simple class C test
_, network, _ := net.ParseCIDR("192.168.0.1/24")
@@ -217,3 +253,38 @@ func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
t.Fatalf("Expected IP %s, got %s", ip1, ip2)
}
}
func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) {
_, netX, _ := net.ParseCIDR(CIDRx)
_, netY, _ := net.ParseCIDR(CIDRy)
if !networkOverlaps(netX, netY) {
t.Errorf("%v and %v should overlap", netX, netY)
}
}
func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) {
_, netX, _ := net.ParseCIDR(CIDRx)
_, netY, _ := net.ParseCIDR(CIDRy)
if networkOverlaps(netX, netY) {
t.Errorf("%v and %v should not overlap", netX, netY)
}
}
func TestNetworkOverlaps(t *testing.T) {
//netY starts at same IP and ends within netX
AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t)
//netY starts within netX and ends at same IP
AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t)
//netY starts and ends within netX
AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t)
//netY starts at same IP and ends outside of netX
AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t)
//netY starts before and ends at same IP of netX
AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
//netY starts before and ends outside of netX
AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
//netY starts and ends before netX
AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t)
//netX starts and ends before netY
AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t)
}
-38
View File
@@ -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
}
+96 -4
View File
@@ -2,6 +2,7 @@ package rcli
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
@@ -15,22 +16,109 @@ import (
var DEBUG_FLAG bool = false
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`,
// issue a single call, and return the result.
// `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)
if err != nil {
return nil, err
}
conn, err := net.Dial(proto, addr)
conn, err := dialDocker(proto, addr)
if err != nil {
return nil, err
}
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
return nil, err
}
return conn.(*net.TCPConn), nil
return conn, nil
}
// 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 {
return err
} else {
conn, err := newDockerServerConn(conn)
if err != nil {
return err
}
go func() {
if DEBUG_FLAG {
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
// is valid.
func Serve(conn io.ReadWriter, service Service) error {
func Serve(conn DockerConn, service Service) error {
r := bufio.NewReader(conn)
var args []string
if line, err := r.ReadString('\n'); err != nil {
+89 -5
View File
@@ -8,15 +8,99 @@ package rcli
// are the usual suspects.
import (
"errors"
"flag"
"fmt"
"github.com/dotcloud/docker/term"
"io"
"log"
"net"
"os"
"reflect"
"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 {
Name() 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
// 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...)
}
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 {
args = []string{"help"}
}
@@ -49,7 +133,7 @@ func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...s
if method != nil {
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 {
@@ -59,7 +143,7 @@ func getMethod(service Service, name string) Cmd {
stdout.Write([]byte(service.Help()))
} else {
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 {
method(stdin, stdout, "--help")
}
+27
View 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)
}
+7 -4
View File
@@ -116,7 +116,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
if err := container.FromDisk(); err != nil {
return nil, err
}
container.State.initLock()
if 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?
// 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 container.State.Running {
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
// Setup state lock (formerly in newState()
container.State.initLock()
// Attach to stdout and stderr
container.stderr = newWriteBroadcaster()
container.stdout = newWriteBroadcaster()
@@ -267,7 +267,10 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
}
netManager, err := newNetworkManager(networkBridgeIface)
if NetworkBridgeIface == "" {
NetworkBridgeIface = DefaultNetworkBridge
}
netManager, err := newNetworkManager(NetworkBridgeIface)
if err != nil {
return nil, err
}
+4 -2
View File
@@ -1,6 +1,7 @@
package docker
import (
"github.com/dotcloud/docker/rcli"
"io"
"io/ioutil"
"os"
@@ -77,7 +78,7 @@ func init() {
runtime: runtime,
}
// 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)
}
}
@@ -314,7 +315,7 @@ func TestRestore(t *testing.T) {
// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
cStdin, _ := container2.StdinPipe()
cStdin.Close()
if err := container2.WaitTimeout(time.Second); err != nil {
if err := container2.WaitTimeout(2 * time.Second); err != nil {
t.Fatal(err)
}
container2.State.Running = true
@@ -358,4 +359,5 @@ func TestRestore(t *testing.T) {
if err := container3.Run(); err != nil {
t.Fatal(err)
}
container2.State.Running = false
}
+6 -17
View File
@@ -11,9 +11,7 @@ type State struct {
Pid int
ExitCode int
StartedAt time.Time
stateChangeLock *sync.Mutex
stateChangeCond *sync.Cond
l *sync.Mutex
}
// String returns a human-readable description of the state
@@ -29,31 +27,22 @@ func (s *State) setRunning(pid int) {
s.ExitCode = 0
s.Pid = pid
s.StartedAt = time.Now()
s.broadcast()
}
func (s *State) setStopped(exitCode int) {
s.Running = false
s.Pid = 0
s.ExitCode = exitCode
s.broadcast()
}
func (s *State) initLock() {
if s.stateChangeLock == nil {
s.stateChangeLock = &sync.Mutex{}
s.stateChangeCond = sync.NewCond(s.stateChangeLock)
}
s.l = &sync.Mutex{}
}
func (s *State) broadcast() {
s.stateChangeLock.Lock()
s.stateChangeCond.Broadcast()
s.stateChangeLock.Unlock()
func (s *State) lock() {
s.l.Lock()
}
func (s *State) wait() {
s.stateChangeLock.Lock()
s.stateChangeCond.Wait()
s.stateChangeLock.Unlock()
func (s *State) unlock() {
s.l.Unlock()
}
+2 -1
View File
@@ -15,7 +15,8 @@ void MakeRaw(int fd) {
ioctl(fd, TCGETS, &t);
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 |= CS8;
+43
View File
@@ -341,3 +341,46 @@ func TruncateId(id string) string {
}
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
}