mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-20 12:45:50 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4fc52305a | |||
| 2aad4a3478 | |||
| a5fb1d6c01 | |||
| f344212b93 | |||
| 0424998f38 | |||
| 8bfbdd7afa | |||
| 3de51b7bfe | |||
| a58cd8c616 | |||
| 586a79cca0 | |||
| 349edf1bea | |||
| 677908910c | |||
| 6b5fe8c2ec | |||
| 26088a72b3 | |||
| ebc837957f | |||
| c4d3da5871 | |||
| 32f5811476 | |||
| a7f191d51d | |||
| 92186d7cf7 | |||
| 5d3c0767da | |||
| 3b65be9127 | |||
| ad0183e419 | |||
| 4f36039e7b | |||
| b74d1c9247 | |||
| cf8b8c1969 | |||
| d1767bbf67 | |||
| 7b74b9cab5 | |||
| 14d3880daf | |||
| 22f1cc955d | |||
| cab31fd512 | |||
| 1fc55c2bb9 | |||
| 5ecd940a59 | |||
| 3b8c2417fb | |||
| a19a9e3ca8 | |||
| ad2bbe23be | |||
| 6882c78ce4 | |||
| 791ca6fde4 | |||
| 43484e8b50 | |||
| 02c211a0dc | |||
| c780ff5ae7 | |||
| 8edf0ca7f3 | |||
| 8c36e6920a | |||
| 3dcaf20d6b |
@@ -21,6 +21,7 @@ Jonathan Rudenberg <jonathan@titanous.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Kevin J. Lynagh <kevin@keminglabs.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Mikhail Sobolev <mss@mawhrin.net>
|
||||
Nelson Chen <crazysim@gmail.com>
|
||||
|
||||
+28
-19
@@ -18,7 +18,7 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.1.2"
|
||||
const VERSION = "0.1.3"
|
||||
|
||||
var GIT_COMMIT string
|
||||
|
||||
@@ -99,7 +99,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||
}
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Fprint(stdout, "Read error: %v\n", err)
|
||||
fmt.Fprintf(stdout, "Read error: %v\n", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -512,10 +512,9 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return err
|
||||
}
|
||||
err = srv.runtime.graph.PushImage(stdout, img, srv.runtime.authConfig)
|
||||
if err != nil {
|
||||
@@ -799,7 +798,8 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
if container == nil {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
return <-container.Attach(stdin, stdout, stdout)
|
||||
|
||||
return <-container.Attach(stdin, nil, stdout, stdout)
|
||||
}
|
||||
|
||||
// Ports type - Used to parse multiple -p flags
|
||||
@@ -833,25 +833,25 @@ func (opts *ListOpts) Set(value string) error {
|
||||
// AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to
|
||||
type AttachOpts map[string]bool
|
||||
|
||||
func NewAttachOpts() *AttachOpts {
|
||||
opts := make(map[string]bool)
|
||||
return (*AttachOpts)(&opts)
|
||||
func NewAttachOpts() AttachOpts {
|
||||
return make(AttachOpts)
|
||||
}
|
||||
|
||||
func (opts *AttachOpts) String() string {
|
||||
return fmt.Sprint(*opts)
|
||||
func (opts AttachOpts) String() string {
|
||||
// Cast to underlying map type to avoid infinite recursion
|
||||
return fmt.Sprintf("%v", map[string]bool(opts))
|
||||
}
|
||||
|
||||
func (opts *AttachOpts) Set(val string) error {
|
||||
func (opts AttachOpts) Set(val string) error {
|
||||
if val != "stdin" && val != "stdout" && val != "stderr" {
|
||||
return fmt.Errorf("Unsupported stream name: %s", val)
|
||||
}
|
||||
(*opts)[val] = true
|
||||
opts[val] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (opts *AttachOpts) Get(val string) bool {
|
||||
if res, exists := (*opts)[val]; exists {
|
||||
func (opts AttachOpts) Get(val string) bool {
|
||||
if res, exists := opts[val]; exists {
|
||||
return res
|
||||
}
|
||||
return false
|
||||
@@ -901,11 +901,17 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
}
|
||||
}
|
||||
var (
|
||||
cStdin io.Reader
|
||||
cStdin io.ReadCloser
|
||||
cStdout, cStderr io.Writer
|
||||
)
|
||||
if config.AttachStdin {
|
||||
cStdin = stdin
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
defer w.Close()
|
||||
defer Debugf("Closing buffered stdin pipe")
|
||||
io.Copy(w, stdin)
|
||||
}()
|
||||
cStdin = r
|
||||
}
|
||||
if config.AttachStdout {
|
||||
cStdout = stdout
|
||||
@@ -913,7 +919,8 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
if config.AttachStderr {
|
||||
cStderr = stdout // FIXME: rcli can't differentiate stdout from stderr
|
||||
}
|
||||
attachErr := container.Attach(cStdin, cStdout, cStderr)
|
||||
|
||||
attachErr := container.Attach(cStdin, stdin, cStdout, cStderr)
|
||||
Debugf("Starting\n")
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
@@ -922,7 +929,9 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
fmt.Fprintln(stdout, container.ShortId())
|
||||
}
|
||||
Debugf("Waiting for attach to return\n")
|
||||
return <-attachErr
|
||||
<-attachErr
|
||||
// Expecting I/O pipe error, discarding
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewServer() (*Server, error) {
|
||||
|
||||
@@ -80,6 +80,57 @@ func TestRunHostname(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunExit(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() {
|
||||
srv.CmdRun(stdin, stdoutPipe, "-i", 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)
|
||||
}
|
||||
})
|
||||
|
||||
container := runtime.List()[0]
|
||||
|
||||
// Closing /bin/cat stdin, expect it to exit
|
||||
p, err := container.StdinPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := p.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// as the process exited, CmdRun must finish and unblock. Wait for it
|
||||
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// Make sure that the client has been disconnected
|
||||
setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
|
||||
// Expecting pipe i/o error, just check that read does not block
|
||||
stdin.Read([]byte{})
|
||||
})
|
||||
|
||||
// Cleanup pipes
|
||||
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Expected behaviour: the process dies when the client disconnects
|
||||
func TestRunDisconnect(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
@@ -237,6 +288,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
|
||||
<-c1
|
||||
})
|
||||
|
||||
// We closed stdin, expect /bin/cat to still be running
|
||||
// Wait a little bit to make sure container.monitor() did his thing
|
||||
err = container.WaitTimeout(500 * time.Millisecond)
|
||||
|
||||
+42
-16
@@ -55,7 +55,7 @@ type Config struct {
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
Ports []int
|
||||
PortSpecs []string
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
@@ -79,8 +79,8 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
|
||||
|
||||
var flPorts ports
|
||||
cmd.Var(&flPorts, "p", "Map a network port to the container")
|
||||
var flPorts ListOpts
|
||||
cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
|
||||
var flEnv ListOpts
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
@@ -88,11 +88,11 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if *flDetach && len(*flAttach) > 0 {
|
||||
if *flDetach && len(flAttach) > 0 {
|
||||
return nil, fmt.Errorf("Conflicting options: -a and -d")
|
||||
}
|
||||
// If neither -d or -a are set, attach to everything by default
|
||||
if len(*flAttach) == 0 && !*flDetach {
|
||||
if len(flAttach) == 0 && !*flDetach {
|
||||
if !*flDetach {
|
||||
flAttach.Set("stdout")
|
||||
flAttach.Set("stderr")
|
||||
@@ -112,7 +112,7 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
|
||||
}
|
||||
config := &Config{
|
||||
Hostname: *flHostname,
|
||||
Ports: flPorts,
|
||||
PortSpecs: flPorts,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
OpenStdin: *flStdin,
|
||||
@@ -257,9 +257,9 @@ func (container *Container) start() error {
|
||||
return container.cmd.Start()
|
||||
}
|
||||
|
||||
func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.Writer) chan error {
|
||||
var cStdout io.ReadCloser
|
||||
var cStderr io.ReadCloser
|
||||
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
||||
var cStdout, cStderr io.ReadCloser
|
||||
|
||||
var nJobs int
|
||||
errors := make(chan error, 3)
|
||||
if stdin != nil && container.Config.OpenStdin {
|
||||
@@ -269,15 +269,23 @@ func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.
|
||||
} else {
|
||||
go func() {
|
||||
Debugf("[start] attach stdin\n")
|
||||
defer Debugf("[end] attach stdin\n")
|
||||
defer Debugf("[end] attach stdin\n")
|
||||
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
||||
if cStdout != nil {
|
||||
defer cStdout.Close()
|
||||
}
|
||||
if cStderr != nil {
|
||||
defer cStderr.Close()
|
||||
}
|
||||
if container.Config.StdinOnce {
|
||||
defer cStdin.Close()
|
||||
}
|
||||
_, err := io.Copy(cStdin, stdin)
|
||||
if err != nil {
|
||||
Debugf("[error] attach stdout: %s\n", err)
|
||||
Debugf("[error] attach stdin: %s\n", err)
|
||||
}
|
||||
errors <- err
|
||||
// Discard error, expecting pipe error
|
||||
errors <- nil
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -290,6 +298,15 @@ func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.
|
||||
go func() {
|
||||
Debugf("[start] attach stdout\n")
|
||||
defer Debugf("[end] attach stdout\n")
|
||||
// If we are in StdinOnce mode, then close stdin
|
||||
if container.Config.StdinOnce {
|
||||
if stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
}
|
||||
_, err := io.Copy(stdout, cStdout)
|
||||
if err != nil {
|
||||
Debugf("[error] attach stdout: %s\n", err)
|
||||
@@ -307,6 +324,15 @@ func (container *Container) Attach(stdin io.Reader, stdout io.Writer, stderr io.
|
||||
go func() {
|
||||
Debugf("[start] attach stderr\n")
|
||||
defer Debugf("[end] attach stderr\n")
|
||||
// If we are in StdinOnce mode, then close stdin
|
||||
if container.Config.StdinOnce {
|
||||
if stdin != nil {
|
||||
defer stdin.Close()
|
||||
}
|
||||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
}
|
||||
_, err := io.Copy(stderr, cStderr)
|
||||
if err != nil {
|
||||
Debugf("[error] attach stderr: %s\n", err)
|
||||
@@ -456,12 +482,12 @@ func (container *Container) allocateNetwork() error {
|
||||
return err
|
||||
}
|
||||
container.NetworkSettings.PortMapping = make(map[string]string)
|
||||
for _, port := range container.Config.Ports {
|
||||
if extPort, err := iface.AllocatePort(port); err != nil {
|
||||
for _, spec := range container.Config.PortSpecs {
|
||||
if nat, err := iface.AllocatePort(spec); err != nil {
|
||||
iface.Release()
|
||||
return err
|
||||
} else {
|
||||
container.NetworkSettings.PortMapping[strconv.Itoa(port)] = strconv.Itoa(extPort)
|
||||
container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend)
|
||||
}
|
||||
}
|
||||
container.network = iface
|
||||
@@ -626,7 +652,7 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (container *Container) EnsureMounted() error {
|
||||
|
||||
@@ -10,312 +10,44 @@ Command Line Interface
|
||||
Docker Usage
|
||||
~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
To list available commands, either run ``docker`` with no parameters or execute
|
||||
``docker help``::
|
||||
|
||||
$ docker
|
||||
Usage: docker COMMAND [arg...]
|
||||
|
||||
A self-sufficient runtime for linux containers.
|
||||
|
||||
Commands:
|
||||
attach Attach to a running container
|
||||
commit Create a new image from a container's changes
|
||||
diff Inspect changes on a container's filesystem
|
||||
export Stream the contents of a container as a tar archive
|
||||
history Show the history of an image
|
||||
images List images
|
||||
import Create a new filesystem image from the contents of a tarball
|
||||
info Display system-wide information
|
||||
inspect Return low-level information on a container
|
||||
kill Kill a running container
|
||||
login Register or Login to the docker registry server
|
||||
logs Fetch the logs of a container
|
||||
port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
ps List containers
|
||||
pull Pull an image or a repository to the docker registry server
|
||||
push Push an image or a repository to the docker registry server
|
||||
restart Restart a running container
|
||||
rm Remove a container
|
||||
rmi Remove an image
|
||||
run Run a command in a new container
|
||||
start Start a stopped container
|
||||
stop Stop a running container
|
||||
tag Tag an image into a repository
|
||||
version Show the docker version information
|
||||
wait Block until a container stops, then print its exit code
|
||||
|
||||
|
||||
attach
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker attach [OPTIONS]
|
||||
|
||||
Attach to a running container
|
||||
|
||||
-e=true: Attach to stderr
|
||||
-i=false: Attach to stdin
|
||||
-o=true: Attach to stdout
|
||||
|
||||
|
||||
commit
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker commit [OPTIONS] CONTAINER [DEST]
|
||||
|
||||
Create a new image from a container's changes
|
||||
|
||||
-m="": Commit message
|
||||
|
||||
|
||||
diff
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker diff CONTAINER [OPTIONS]
|
||||
|
||||
Inspect changes on a container's filesystem
|
||||
|
||||
|
||||
export
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker export CONTAINER
|
||||
|
||||
Export the contents of a filesystem as a tar archive
|
||||
|
||||
|
||||
history
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker history [OPTIONS] IMAGE
|
||||
|
||||
Show the history of an image
|
||||
|
||||
|
||||
images
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker images [OPTIONS] [NAME]
|
||||
|
||||
List images
|
||||
|
||||
-a=false: show all images
|
||||
-q=false: only show numeric IDs
|
||||
|
||||
|
||||
import
|
||||
~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
|
||||
|
||||
Create a new filesystem image from the contents of a tarball
|
||||
|
||||
|
||||
info
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker info
|
||||
|
||||
Display system-wide information.
|
||||
|
||||
|
||||
inspect
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker inspect [OPTIONS] CONTAINER
|
||||
|
||||
Return low-level information on a container
|
||||
|
||||
|
||||
kill
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Kill a running container
|
||||
|
||||
|
||||
login
|
||||
~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker login
|
||||
|
||||
Register or Login to the docker registry server
|
||||
|
||||
|
||||
logs
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker logs [OPTIONS] CONTAINER
|
||||
|
||||
Fetch the logs of a container
|
||||
|
||||
|
||||
port
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT
|
||||
|
||||
Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
|
||||
|
||||
ps
|
||||
~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker ps [OPTIONS]
|
||||
|
||||
List containers
|
||||
|
||||
-a=false: Show all containers. Only running containers are shown by default.
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: Only display numeric IDs
|
||||
|
||||
|
||||
pull
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker pull NAME
|
||||
|
||||
Pull an image or a repository from the registry
|
||||
|
||||
push
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker push NAME
|
||||
|
||||
Push an image or a repository to the registry
|
||||
|
||||
|
||||
restart
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker restart [OPTIONS] NAME
|
||||
|
||||
Restart a running container
|
||||
|
||||
|
||||
rm
|
||||
~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rm [OPTIONS] CONTAINER
|
||||
|
||||
Remove a container
|
||||
|
||||
|
||||
rmi
|
||||
~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rmi [OPTIONS] IMAGE
|
||||
|
||||
Remove an image
|
||||
|
||||
-a=false: Use IMAGE as a path and remove ALL images in this path
|
||||
-r=false: Use IMAGE as a regular expression instead of an exact name
|
||||
|
||||
|
||||
run
|
||||
~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
|
||||
|
||||
Run a command in a new container
|
||||
|
||||
-c="": Comment
|
||||
-i=false: Keep stdin open even if not attached
|
||||
-m=0: Memory limit (in bytes)
|
||||
-p=[]: Map a network port to the container
|
||||
-t=false: Allocate a pseudo-tty
|
||||
-h="": Container host name
|
||||
-u="": Username or UID
|
||||
|
||||
|
||||
start
|
||||
~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker start [OPTIONS] NAME
|
||||
|
||||
Start a stopped container
|
||||
|
||||
|
||||
stop
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker stop [OPTIONS] NAME
|
||||
|
||||
Stop a running container
|
||||
|
||||
|
||||
tag
|
||||
~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
|
||||
|
||||
Tag an image into a repository
|
||||
|
||||
-f=false: Force
|
||||
|
||||
|
||||
version
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker version
|
||||
|
||||
Show the docker version information
|
||||
|
||||
|
||||
wait
|
||||
~~~~
|
||||
|
||||
::
|
||||
|
||||
Usage: docker wait [OPTIONS] NAME
|
||||
|
||||
Block until a container stops, then print its exit code.
|
||||
|
||||
...
|
||||
|
||||
Available Commands
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
command/attach
|
||||
command/commit
|
||||
command/diff
|
||||
command/export
|
||||
command/history
|
||||
command/images
|
||||
command/import
|
||||
command/info
|
||||
command/inspect
|
||||
command/kill
|
||||
command/login
|
||||
command/logs
|
||||
command/port
|
||||
command/ps
|
||||
command/pull
|
||||
command/push
|
||||
command/restart
|
||||
command/rm
|
||||
command/rmi
|
||||
command/run
|
||||
command/start
|
||||
command/stop
|
||||
command/tag
|
||||
command/version
|
||||
command/wait
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
===========================================
|
||||
``attach`` -- Attach to a running container
|
||||
===========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker attach CONTAINER
|
||||
|
||||
Attach to a running container
|
||||
@@ -0,0 +1,11 @@
|
||||
===========================================================
|
||||
``commit`` -- Create a new image from a container's changes
|
||||
===========================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY [TAG]]
|
||||
|
||||
Create a new image from a container's changes
|
||||
|
||||
-m="": Commit message
|
||||
@@ -0,0 +1,9 @@
|
||||
=======================================================
|
||||
``diff`` -- Inspect changes on a container's filesystem
|
||||
=======================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker diff CONTAINER [OPTIONS]
|
||||
|
||||
Inspect changes on a container's filesystem
|
||||
@@ -0,0 +1,9 @@
|
||||
=================================================================
|
||||
``export`` -- Stream the contents of a container as a tar archive
|
||||
=================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker export CONTAINER
|
||||
|
||||
Export the contents of a filesystem as a tar archive
|
||||
@@ -0,0 +1,9 @@
|
||||
===========================================
|
||||
``history`` -- Show the history of an image
|
||||
===========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker history [OPTIONS] IMAGE
|
||||
|
||||
Show the history of an image
|
||||
@@ -0,0 +1,12 @@
|
||||
=========================
|
||||
``images`` -- List images
|
||||
=========================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker images [OPTIONS] [NAME]
|
||||
|
||||
List images
|
||||
|
||||
-a=false: show all images
|
||||
-q=false: only show numeric IDs
|
||||
@@ -0,0 +1,9 @@
|
||||
==========================================================================
|
||||
``import`` -- Create a new filesystem image from the contents of a tarball
|
||||
==========================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker import [OPTIONS] URL|- [REPOSITORY [TAG]]
|
||||
|
||||
Create a new filesystem image from the contents of a tarball
|
||||
@@ -0,0 +1,9 @@
|
||||
===========================================
|
||||
``info`` -- Display system-wide information
|
||||
===========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker info
|
||||
|
||||
Display system-wide information.
|
||||
@@ -0,0 +1,9 @@
|
||||
==========================================================
|
||||
``inspect`` -- Return low-level information on a container
|
||||
==========================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker inspect [OPTIONS] CONTAINER
|
||||
|
||||
Return low-level information on a container
|
||||
@@ -0,0 +1,9 @@
|
||||
====================================
|
||||
``kill`` -- Kill a running container
|
||||
====================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||
|
||||
Kill a running container
|
||||
@@ -0,0 +1,9 @@
|
||||
============================================================
|
||||
``login`` -- Register or Login to the docker registry server
|
||||
============================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker login
|
||||
|
||||
Register or Login to the docker registry server
|
||||
@@ -0,0 +1,9 @@
|
||||
=========================================
|
||||
``logs`` -- Fetch the logs of a container
|
||||
=========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker logs [OPTIONS] CONTAINER
|
||||
|
||||
Fetch the logs of a container
|
||||
@@ -0,0 +1,9 @@
|
||||
=========================================================================
|
||||
``port`` -- Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
=========================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT
|
||||
|
||||
Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
|
||||
@@ -0,0 +1,13 @@
|
||||
=========================
|
||||
``ps`` -- List containers
|
||||
=========================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker ps [OPTIONS]
|
||||
|
||||
List containers
|
||||
|
||||
-a=false: Show all containers. Only running containers are shown by default.
|
||||
-notrunc=false: Don't truncate output
|
||||
-q=false: Only display numeric IDs
|
||||
@@ -0,0 +1,9 @@
|
||||
=========================================================================
|
||||
``pull`` -- Pull an image or a repository from the docker registry server
|
||||
=========================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker pull NAME
|
||||
|
||||
Pull an image or a repository from the registry
|
||||
@@ -0,0 +1,9 @@
|
||||
=======================================================================
|
||||
``push`` -- Push an image or a repository to the docker registry server
|
||||
=======================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker push NAME
|
||||
|
||||
Push an image or a repository to the registry
|
||||
@@ -0,0 +1,9 @@
|
||||
==========================================
|
||||
``restart`` -- Restart a running container
|
||||
==========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker restart [OPTIONS] NAME
|
||||
|
||||
Restart a running container
|
||||
@@ -0,0 +1,9 @@
|
||||
============================
|
||||
``rm`` -- Remove a container
|
||||
============================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rm [OPTIONS] CONTAINER
|
||||
|
||||
Remove a container
|
||||
@@ -0,0 +1,9 @@
|
||||
==========================
|
||||
``rmi`` -- Remove an image
|
||||
==========================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker rmimage [OPTIONS] IMAGE
|
||||
|
||||
Remove an image
|
||||
@@ -0,0 +1,19 @@
|
||||
===========================================
|
||||
``run`` -- Run a command in a new container
|
||||
===========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
|
||||
|
||||
Run a command in a new container
|
||||
|
||||
-a=map[]: Attach to stdin, stdout or stderr.
|
||||
-d=false: Detached mode: leave the container running in the background
|
||||
-e=[]: Set environment variables
|
||||
-h="": Container host name
|
||||
-i=false: Keep stdin open even if not attached
|
||||
-m=0: Memory limit (in bytes)
|
||||
-p=[]: Map a network port to the container
|
||||
-t=false: Allocate a pseudo-tty
|
||||
-u="": Username or UID
|
||||
@@ -0,0 +1,9 @@
|
||||
======================================
|
||||
``start`` -- Start a stopped container
|
||||
======================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker start [OPTIONS] NAME
|
||||
|
||||
Start a stopped container
|
||||
@@ -0,0 +1,9 @@
|
||||
====================================
|
||||
``stop`` -- Stop a running container
|
||||
====================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker stop [OPTIONS] NAME
|
||||
|
||||
Stop a running container
|
||||
@@ -0,0 +1,11 @@
|
||||
=========================================
|
||||
``tag`` -- Tag an image into a repository
|
||||
=========================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
|
||||
|
||||
Tag an image into a repository
|
||||
|
||||
-f=false: Force
|
||||
@@ -0,0 +1,3 @@
|
||||
==================================================
|
||||
``version`` -- Show the docker version information
|
||||
==================================================
|
||||
@@ -0,0 +1,9 @@
|
||||
===================================================================
|
||||
``wait`` -- Block until a container stops, then print its exit code
|
||||
===================================================================
|
||||
|
||||
::
|
||||
|
||||
Usage: docker wait [OPTIONS] NAME
|
||||
|
||||
Block until a container stops, then print its exit code.
|
||||
@@ -15,4 +15,4 @@ Contents:
|
||||
hello_world
|
||||
hello_world_daemon
|
||||
python_web_app
|
||||
runningsshservice
|
||||
running_ssh_service
|
||||
|
||||
@@ -65,6 +65,4 @@ See the example in action
|
||||
<iframe width="720" height="350" src="http://ascii.io/a/2573/raw" frameborder="0"></iframe>
|
||||
</div>
|
||||
|
||||
Continue to the `base commands`_
|
||||
|
||||
.. _base commands: ../commandline/basecommands.html
|
||||
Continue to :ref:`running_ssh_service`.
|
||||
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
:title: Running an SSH service
|
||||
:description: A screencast of installing and running an sshd service
|
||||
:keywords: docker, example, package installation, networking
|
||||
|
||||
.. _running_ssh_service:
|
||||
|
||||
Create an ssh daemon service
|
||||
============================
|
||||
@@ -85,9 +85,10 @@ func (graph *Graph) Get(name string) (*Image, error) {
|
||||
// Create creates a new image and registers it in the graph.
|
||||
func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) {
|
||||
img := &Image{
|
||||
Id: GenerateId(),
|
||||
Comment: comment,
|
||||
Created: time.Now(),
|
||||
Id: GenerateId(),
|
||||
Comment: comment,
|
||||
Created: time.Now(),
|
||||
DockerVersion: VERSION,
|
||||
}
|
||||
if container != nil {
|
||||
img.Parent = container.Image
|
||||
@@ -129,6 +130,9 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
|
||||
|
||||
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
|
||||
func (graph *Graph) Mktemp(id string) (string, error) {
|
||||
if id == "" {
|
||||
id = GenerateId()
|
||||
}
|
||||
tmp, err := NewGraph(path.Join(graph.Root, ":tmp:"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Couldn't create temp: %s", err)
|
||||
@@ -139,13 +143,6 @@ func (graph *Graph) Mktemp(id string) (string, error) {
|
||||
return tmp.imageRoot(id), nil
|
||||
}
|
||||
|
||||
// Garbage returns the "garbage", a staging area for deleted images.
|
||||
// This allows images to be deleted atomically by os.Rename(), instead of
|
||||
// os.RemoveAll() which is prone to race conditions.
|
||||
func (graph *Graph) Garbage() (*Graph, error) {
|
||||
return NewGraph(path.Join(graph.Root, ":garbage:"))
|
||||
}
|
||||
|
||||
// Check if given error is "not empty".
|
||||
// Note: this is the way golang does it internally with os.IsNotExists.
|
||||
func isNotEmpty(err error) bool {
|
||||
@@ -166,54 +163,16 @@ func (graph *Graph) Delete(name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
garbage, err := graph.Garbage()
|
||||
tmp, err := graph.Mktemp("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
graph.idIndex.Delete(id)
|
||||
err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id))
|
||||
if err != nil {
|
||||
// FIXME: this introduces a race condition in Delete() if the image is already present
|
||||
// in garbage. Let's store at random names in grabage instead.
|
||||
if isNotEmpty(err) {
|
||||
Debugf("The image %s is already present in garbage. Removing it.", id)
|
||||
if err = os.RemoveAll(garbage.imageRoot(id)); err != nil {
|
||||
Debugf("Error while removing the image %s from garbage: %s\n", id, err)
|
||||
return err
|
||||
}
|
||||
Debugf("Image %s removed from garbage", id)
|
||||
if err = os.Rename(graph.imageRoot(id), garbage.imageRoot(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
Debugf("Image %s put in the garbage", id)
|
||||
} else {
|
||||
Debugf("Error putting the image %s to garbage: %s\n", id, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Undelete moves an image back from the garbage to the main graph.
|
||||
func (graph *Graph) Undelete(id string) error {
|
||||
garbage, err := graph.Garbage()
|
||||
err = os.Rename(graph.imageRoot(id), tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(garbage.imageRoot(id), graph.imageRoot(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
graph.idIndex.Add(id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GarbageCollect definitely deletes all images moved to the garbage.
|
||||
func (graph *Graph) GarbageCollect() error {
|
||||
garbage, err := graph.Garbage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(garbage.Root)
|
||||
return os.RemoveAll(tmp)
|
||||
}
|
||||
|
||||
// Map returns a list of all images in the graph, addressable by ID.
|
||||
|
||||
@@ -45,6 +45,9 @@ func TestGraphCreate(t *testing.T) {
|
||||
if image.Comment != "Testing" {
|
||||
t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment)
|
||||
}
|
||||
if image.DockerVersion != VERSION {
|
||||
t.Fatalf("Wrong docker_version: should be '%s', not '%s'", VERSION, image.DockerVersion)
|
||||
}
|
||||
if images, err := graph.All(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if l := len(images); l != 1 {
|
||||
|
||||
@@ -20,6 +20,7 @@ type Image struct {
|
||||
Created time.Time `json:"created"`
|
||||
Container string `json:"container,omitempty"`
|
||||
ContainerConfig Config `json:"container_config,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
graph *Graph
|
||||
}
|
||||
|
||||
|
||||
+89
-36
@@ -9,6 +9,7 @@ import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -98,6 +99,9 @@ type PortMapper struct {
|
||||
|
||||
func (mapper *PortMapper) cleanup() error {
|
||||
// Ignore errors - This could mean the chains were never set up
|
||||
iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
|
||||
// Also cleanup rules created by older versions, or -X might fail.
|
||||
iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
|
||||
iptables("-t", "nat", "-F", "DOCKER")
|
||||
@@ -110,10 +114,10 @@ func (mapper *PortMapper) setup() error {
|
||||
if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to create DOCKER chain: %s", err)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "PREROUTING", "-j", "DOCKER"); err != nil {
|
||||
if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "OUTPUT", "-j", "DOCKER"); err != nil {
|
||||
if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
|
||||
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
|
||||
}
|
||||
return nil
|
||||
@@ -157,39 +161,55 @@ func newPortMapper() (*PortMapper, error) {
|
||||
|
||||
// Port allocator: Atomatically allocate and release networking ports
|
||||
type PortAllocator struct {
|
||||
ports chan (int)
|
||||
inUse map[int]struct{}
|
||||
fountain chan (int)
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (alloc *PortAllocator) populate(start, end int) {
|
||||
alloc.ports = make(chan int, end-start)
|
||||
for port := start; port < end; port++ {
|
||||
alloc.ports <- port
|
||||
func (alloc *PortAllocator) runFountain() {
|
||||
if alloc.fountain != nil {
|
||||
return
|
||||
}
|
||||
alloc.fountain = make(chan int)
|
||||
for {
|
||||
for port := portRangeStart; port < portRangeEnd; port++ {
|
||||
alloc.fountain <- port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *PortAllocator) Acquire() (int, error) {
|
||||
select {
|
||||
case port := <-alloc.ports:
|
||||
return port, nil
|
||||
default:
|
||||
return -1, errors.New("No more ports available")
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// FIXME: Release can no longer fail, change its prototype to reflect that.
|
||||
func (alloc *PortAllocator) Release(port int) error {
|
||||
select {
|
||||
case alloc.ports <- port:
|
||||
return nil
|
||||
default:
|
||||
return errors.New("Too many ports have been released")
|
||||
}
|
||||
alloc.lock.Lock()
|
||||
delete(alloc.inUse, port)
|
||||
alloc.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPortAllocator(start, end int) (*PortAllocator, error) {
|
||||
allocator := &PortAllocator{}
|
||||
allocator.populate(start, end)
|
||||
func (alloc *PortAllocator) Acquire(port int) (int, error) {
|
||||
if port == 0 {
|
||||
// Allocate a port from the fountain
|
||||
for port := range alloc.fountain {
|
||||
if _, err := alloc.Acquire(port); err == nil {
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("Port generator ended unexpectedly")
|
||||
}
|
||||
alloc.lock.Lock()
|
||||
defer alloc.lock.Unlock()
|
||||
if _, inUse := alloc.inUse[port]; inUse {
|
||||
return -1, fmt.Errorf("Port already in use: %d", port)
|
||||
}
|
||||
alloc.inUse[port] = struct{}{}
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func newPortAllocator() (*PortAllocator, error) {
|
||||
allocator := &PortAllocator{
|
||||
inUse: make(map[int]struct{}),
|
||||
}
|
||||
go allocator.runFountain()
|
||||
return allocator, nil
|
||||
}
|
||||
|
||||
@@ -298,17 +318,50 @@ type NetworkInterface struct {
|
||||
}
|
||||
|
||||
// Allocate an external TCP port and map it to the interface
|
||||
func (iface *NetworkInterface) AllocatePort(port int) (int, error) {
|
||||
extPort, err := iface.manager.portAllocator.Acquire()
|
||||
func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
|
||||
nat, err := parseNat(spec)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
return nil, err
|
||||
}
|
||||
if err := iface.manager.portMapper.Map(extPort, net.TCPAddr{IP: iface.IPNet.IP, Port: port}); err != nil {
|
||||
iface.manager.portAllocator.Release(extPort)
|
||||
return -1, err
|
||||
// Allocate a random port if Frontend==0
|
||||
if extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
nat.Frontend = extPort
|
||||
}
|
||||
iface.extPorts = append(iface.extPorts, extPort)
|
||||
return extPort, nil
|
||||
if err := iface.manager.portMapper.Map(nat.Frontend, net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}); err != nil {
|
||||
iface.manager.portAllocator.Release(nat.Frontend)
|
||||
return nil, err
|
||||
}
|
||||
iface.extPorts = append(iface.extPorts, nat.Frontend)
|
||||
return nat, nil
|
||||
}
|
||||
|
||||
type Nat struct {
|
||||
Proto string
|
||||
Frontend int
|
||||
Backend int
|
||||
}
|
||||
|
||||
func parseNat(spec string) (*Nat, error) {
|
||||
var nat Nat
|
||||
// If spec starts with ':', external and internal ports must be the same.
|
||||
// This might fail if the requested external port is not available.
|
||||
var sameFrontend bool
|
||||
if spec[0] == ':' {
|
||||
sameFrontend = true
|
||||
spec = spec[1:]
|
||||
}
|
||||
port, err := strconv.ParseUint(spec, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nat.Backend = int(port)
|
||||
if sameFrontend {
|
||||
nat.Frontend = nat.Backend
|
||||
}
|
||||
nat.Proto = "tcp"
|
||||
return &nat, nil
|
||||
}
|
||||
|
||||
// Release: Network cleanup - release all resources
|
||||
@@ -354,13 +407,13 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
|
||||
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
|
||||
addr, err := getIfaceAddr(bridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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)
|
||||
}
|
||||
network := addr.(*net.IPNet)
|
||||
|
||||
ipAllocator := newIPAllocator(network)
|
||||
|
||||
portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd)
|
||||
portAllocator, err := newPortAllocator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+21
-5
@@ -7,9 +7,10 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"sort"
|
||||
"sync"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -115,6 +116,7 @@ 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)
|
||||
}
|
||||
@@ -132,11 +134,26 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||
if err := validateId(container.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// 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 {
|
||||
return err
|
||||
} else {
|
||||
if !strings.Contains(string(output), "RUNNING") {
|
||||
Debugf("Container %s was supposed to be running be is not.", container.Id)
|
||||
container.State.setStopped(-127)
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container.runtime = runtime
|
||||
// Setup state lock (formerly in newState()
|
||||
lock := new(sync.Mutex)
|
||||
container.State.stateChangeLock = lock
|
||||
container.State.stateChangeCond = sync.NewCond(lock)
|
||||
container.State.initLock()
|
||||
// Attach to stdout and stderr
|
||||
container.stderr = newWriteBroadcaster()
|
||||
container.stdout = newWriteBroadcaster()
|
||||
@@ -259,7 +276,6 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
||||
// If the auth file does not exist, keep going
|
||||
return nil, err
|
||||
}
|
||||
|
||||
runtime := &Runtime{
|
||||
root: root,
|
||||
repository: runtimeRepo,
|
||||
|
||||
+57
-12
@@ -8,6 +8,7 @@ import (
|
||||
"os/user"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FIXME: this is no longer needed
|
||||
@@ -21,10 +22,10 @@ func nuke(runtime *Runtime) error {
|
||||
var wg sync.WaitGroup
|
||||
for _, container := range runtime.List() {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
container.Kill()
|
||||
wg.Add(-1)
|
||||
}()
|
||||
go func(c *Container) {
|
||||
c.Kill()
|
||||
wg.Done()
|
||||
}(container)
|
||||
}
|
||||
wg.Wait()
|
||||
return os.RemoveAll(runtime.root)
|
||||
@@ -90,7 +91,6 @@ func newTestRuntime() (*Runtime, error) {
|
||||
return nil, err
|
||||
}
|
||||
if err := CopyDirectory(unitTestStoreBase, root); err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -289,13 +289,48 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime1.Destroy(container1)
|
||||
if len(runtime1.List()) != 1 {
|
||||
t.Errorf("Expected 1 container, %v found", len(runtime1.List()))
|
||||
|
||||
// Create a second container meant to be killed
|
||||
container2, err := runtime1.Create(&Config{
|
||||
Image: GetTestImage(runtime1).Id,
|
||||
Cmd: []string{"/bin/cat"},
|
||||
OpenStdin: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime1.Destroy(container2)
|
||||
|
||||
// Start the container non blocking
|
||||
if err := container2.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.State.Running = true
|
||||
container2.ToDisk()
|
||||
|
||||
if len(runtime1.List()) != 2 {
|
||||
t.Errorf("Expected 2 container, %v found", len(runtime1.List()))
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.Id)
|
||||
}
|
||||
|
||||
// Here are are simulating a docker restart - that is, reloading all containers
|
||||
// from scratch
|
||||
runtime2, err := NewRuntimeFromDirectory(root)
|
||||
@@ -303,14 +338,24 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer nuke(runtime2)
|
||||
if len(runtime2.List()) != 1 {
|
||||
t.Errorf("Expected 1 container, %v found", len(runtime2.List()))
|
||||
if len(runtime2.List()) != 2 {
|
||||
t.Errorf("Expected 2 container, %v found", len(runtime2.List()))
|
||||
}
|
||||
container2 := runtime2.Get(container1.Id)
|
||||
if container2 == nil {
|
||||
runningCount := 0
|
||||
for _, c := range runtime2.List() {
|
||||
if c.State.Running {
|
||||
t.Errorf("Running container found: %v (%v)", c.Id, c.Path)
|
||||
runningCount++
|
||||
}
|
||||
}
|
||||
if runningCount != 0 {
|
||||
t.Fatalf("Expected 0 container alive, %d found", runningCount)
|
||||
}
|
||||
container3 := runtime2.Get(container1.Id)
|
||||
if container3 == nil {
|
||||
t.Fatal("Unable to Get container")
|
||||
}
|
||||
if err := container2.Run(); err != nil {
|
||||
if err := container3.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,13 @@ func (s *State) setStopped(exitCode int) {
|
||||
s.broadcast()
|
||||
}
|
||||
|
||||
func (s *State) initLock() {
|
||||
if s.stateChangeLock == nil {
|
||||
s.stateChangeLock = &sync.Mutex{}
|
||||
s.stateChangeCond = sync.NewCond(s.stateChangeLock)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) broadcast() {
|
||||
s.stateChangeLock.Lock()
|
||||
s.stateChangeCond.Broadcast()
|
||||
|
||||
Reference in New Issue
Block a user