1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

bump master

This commit is contained in:
Victor Vieux 2013-07-24 12:18:53 +00:00
commit 4bc3328e80
20 changed files with 404 additions and 77 deletions

View file

@ -76,6 +76,7 @@ Shawn Siefkas <shawn.siefkas@meredith.com>
Silas Sewell <silas@sewell.org> Silas Sewell <silas@sewell.org>
Solomon Hykes <solomon@dotcloud.com> Solomon Hykes <solomon@dotcloud.com>
Sridhar Ratnakumar <sridharr@activestate.com> Sridhar Ratnakumar <sridharr@activestate.com>
Stefan Praszalowicz <stefan@greplin.com>
Thatcher Peskens <thatcher@dotcloud.com> Thatcher Peskens <thatcher@dotcloud.com>
Thomas Bikeev <thomas.bikeev@mac.com> Thomas Bikeev <thomas.bikeev@mac.com>
Thomas Hansen <thomas.hansen@gmail.com> Thomas Hansen <thomas.hansen@gmail.com>

View file

@ -11,7 +11,7 @@ BUILD_DIR := $(CURDIR)/.gopath
GOPATH ?= $(BUILD_DIR) GOPATH ?= $(BUILD_DIR)
export GOPATH export GOPATH
GO_OPTIONS ?= GO_OPTIONS ?= -a -ldflags='-w -d'
ifeq ($(VERBOSE), 1) ifeq ($(VERBOSE), 1)
GO_OPTIONS += -v GO_OPTIONS += -v
endif endif
@ -80,10 +80,10 @@ test:
tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH} tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH}
GOPATH=${CURDIR}/${BUILD_SRC} go get -d GOPATH=${CURDIR}/${BUILD_SRC} go get -d
# Do the test # Do the test
sudo -E GOPATH=${CURDIR}/${BUILD_SRC} go test ${GO_OPTIONS} sudo -E GOPATH=${CURDIR}/${BUILD_SRC} CGO_ENABLED=0 go test ${GO_OPTIONS}
testall: all testall: all
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS)) @(cd $(DOCKER_DIR); CGO_ENABLED=0 sudo -E go test ./... $(GO_OPTIONS))
fmt: fmt:
@gofmt -s -l -w . @gofmt -s -l -w .

61
api.go
View file

@ -178,6 +178,64 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
return nil return nil
} }
func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) (error) {
b, err := json.Marshal(event)
if err != nil {
return fmt.Errorf("JSON error")
}
_, err = wf.Write(b)
if err != nil {
// On error, evict the listener
utils.Debugf("%s", err)
srv.Lock()
delete(srv.listeners, r.RemoteAddr)
srv.Unlock()
return err
}
return nil
}
if err := parseForm(r); err != nil {
return err
}
listener := make(chan utils.JSONMessage)
srv.Lock()
srv.listeners[r.RemoteAddr] = listener
srv.Unlock()
since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0)
if err != nil {
since = 0
}
w.Header().Set("Content-Type", "application/json")
wf := utils.NewWriteFlusher(w)
if since != 0 {
// If since, send previous events that happened after the timestamp
for _, event := range srv.events {
if event.Time >= since {
err := sendEvent(wf, &event)
if err != nil && err.Error() == "JSON error" {
continue
}
if err != nil {
return err
}
}
}
}
for {
event := <-listener
err := sendEvent(wf, &event)
if err != nil && err.Error() == "JSON error" {
continue
}
if err != nil {
return err
}
}
return nil
}
func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
@ -807,8 +865,9 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
"GET": { "GET": {
"/version": getVersion, "/events": getEvents,
"/info": getInfo, "/info": getInfo,
"/version": getVersion,
"/images/json": getImagesJSON, "/images/json": getImagesJSON,
"/images/viz": getImagesViz, "/images/viz": getImagesViz,
"/images/search": getImagesSearch, "/images/search": getImagesSearch,

View file

@ -17,13 +17,14 @@ type APIImages struct {
} }
type APIInfo struct { type APIInfo struct {
Debug bool Debug bool
Containers int Containers int
Images int Images int
NFd int `json:",omitempty"` NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"` NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"` MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"` SwapLimit bool `json:",omitempty"`
NEventsListener int `json:",omitempty"`
} }
type APITop struct { type APITop struct {

View file

@ -89,6 +89,44 @@ func TestGetInfo(t *testing.T) {
} }
} }
func TestGetEvents(t *testing.T) {
runtime := mkRuntime(t)
srv := &Server{
runtime: runtime,
events: make([]utils.JSONMessage, 0, 64),
listeners: make(map[string]chan utils.JSONMessage),
}
srv.LogEvent("fakeaction", "fakeid")
srv.LogEvent("fakeaction2", "fakeid")
req, err := http.NewRequest("GET", "/events?since=1", nil)
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
setTimeout(t, "", 500*time.Millisecond, func() {
if err := getEvents(srv, APIVERSION, r, req, nil); err != nil {
t.Fatal(err)
}
})
dec := json.NewDecoder(r.Body)
for i := 0; i < 2; i++ {
var jm utils.JSONMessage
if err := dec.Decode(&jm); err == io.EOF {
break
} else if err != nil {
t.Fatal(err)
}
if jm != srv.events[i] {
t.Fatalf("Event received it different than expected")
}
}
}
func TestGetImagesJSON(t *testing.T) { func TestGetImagesJSON(t *testing.T) {
runtime := mkRuntime(t) runtime := mkRuntime(t)
defer nuke(runtime) defer nuke(runtime)

View file

@ -78,6 +78,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"build", "Build a container from a Dockerfile"}, {"build", "Build a container from a Dockerfile"},
{"commit", "Create a new image from a container's changes"}, {"commit", "Create a new image from a container's changes"},
{"diff", "Inspect changes on a container's filesystem"}, {"diff", "Inspect changes on a container's filesystem"},
{"events", "Get real time events from the server"},
{"export", "Stream the contents of a container as a tar archive"}, {"export", "Stream the contents of a container as a tar archive"},
{"history", "Show the history of an image"}, {"history", "Show the history of an image"},
{"images", "List images"}, {"images", "List images"},
@ -470,6 +471,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd) fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd)
fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines) fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines)
fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener)
} }
if !out.MemoryLimit { if !out.MemoryLimit {
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
@ -1059,6 +1061,29 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
return nil return nil
} }
func (cli *DockerCli) CmdEvents(args ...string) error {
cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
since := cmd.String("since", "", "Show events previously created (used for polling).")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 0 {
cmd.Usage()
return nil
}
v := url.Values{}
if *since != "" {
v.Set("since", *since)
}
if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdExport(args ...string) error { func (cli *DockerCli) CmdExport(args ...string) error {
cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive") cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
@ -1513,19 +1538,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
if resp.Header.Get("Content-Type") == "application/json" { if resp.Header.Get("Content-Type") == "application/json" {
dec := json.NewDecoder(resp.Body) dec := json.NewDecoder(resp.Body)
for { for {
var m utils.JSONMessage var jm utils.JSONMessage
if err := dec.Decode(&m); err == io.EOF { if err := dec.Decode(&jm); err == io.EOF {
break break
} else if err != nil { } else if err != nil {
return err return err
} }
if m.Progress != "" { jm.Display(out)
fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress)
} else if m.Error != "" {
return fmt.Errorf(m.Error)
} else {
fmt.Fprintf(out, "%s\n", m.Status)
}
} }
} else { } else {
if _, err := io.Copy(out, resp.Body); err != nil { if _, err := io.Copy(out, resp.Body); err != nil {

View file

@ -38,7 +38,7 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
f() f()
c <- false c <- false
}() }()
if <-c { if <-c && msg != "" {
t.Fatal(msg) t.Fatal(msg)
} }
} }

View file

@ -58,25 +58,26 @@ type Container struct {
} }
type Config struct { type Config struct {
Hostname string Hostname string
User string User string
Memory int64 // Memory limit (in bytes) Memory int64 // Memory limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
CpuShares int64 // CPU shares (relative weight vs. other containers) CpuShares int64 // CPU shares (relative weight vs. other containers)
AttachStdin bool AttachStdin bool
AttachStdout bool AttachStdout bool
AttachStderr bool AttachStderr bool
PortSpecs []string PortSpecs []string
Tty bool // Attach standard streams to a tty, including stdin if it is not closed. Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects. StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
Env []string Env []string
Cmd []string Cmd []string
Dns []string Dns []string
Image string // Name of the image as it was passed by the operator (eg. could be symbolic) Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{} Volumes map[string]struct{}
VolumesFrom string VolumesFrom string
Entrypoint []string Entrypoint []string
NetworkDisabled bool
} }
type HostConfig struct { type HostConfig struct {
@ -106,6 +107,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file") flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
@ -174,23 +176,24 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
} }
config := &Config{ config := &Config{
Hostname: *flHostname, Hostname: *flHostname,
PortSpecs: flPorts, PortSpecs: flPorts,
User: *flUser, User: *flUser,
Tty: *flTty, Tty: *flTty,
OpenStdin: *flStdin, NetworkDisabled: !*flNetwork,
Memory: *flMemory, OpenStdin: *flStdin,
CpuShares: *flCpuShares, Memory: *flMemory,
AttachStdin: flAttach.Get("stdin"), CpuShares: *flCpuShares,
AttachStdout: flAttach.Get("stdout"), AttachStdin: flAttach.Get("stdin"),
AttachStderr: flAttach.Get("stderr"), AttachStdout: flAttach.Get("stdout"),
Env: flEnv, AttachStderr: flAttach.Get("stderr"),
Cmd: runCmd, Env: flEnv,
Dns: flDns, Cmd: runCmd,
Image: image, Dns: flDns,
Volumes: flVolumes, Image: image,
VolumesFrom: *flVolumesFrom, Volumes: flVolumes,
Entrypoint: entrypoint, VolumesFrom: *flVolumesFrom,
Entrypoint: entrypoint,
} }
hostConfig := &HostConfig{ hostConfig := &HostConfig{
Binds: binds, Binds: binds,
@ -511,8 +514,12 @@ func (container *Container) Start(hostConfig *HostConfig) error {
if err := container.EnsureMounted(); err != nil { if err := container.EnsureMounted(); err != nil {
return err return err
} }
if err := container.allocateNetwork(); err != nil { if container.runtime.networkManager.disabled {
return err container.Config.NetworkDisabled = true
} else {
if err := container.allocateNetwork(); err != nil {
return err
}
} }
// Make sure the config is compatible with the current kernel // Make sure the config is compatible with the current kernel
@ -626,7 +633,9 @@ func (container *Container) Start(hostConfig *HostConfig) error {
} }
// Networking // Networking
params = append(params, "-g", container.network.Gateway.String()) if !container.Config.NetworkDisabled {
params = append(params, "-g", container.network.Gateway.String())
}
// User // User
if container.Config.User != "" { if container.Config.User != "" {
@ -728,6 +737,10 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
} }
func (container *Container) allocateNetwork() error { func (container *Container) allocateNetwork() error {
if container.Config.NetworkDisabled {
return nil
}
iface, err := container.runtime.networkManager.Allocate() iface, err := container.runtime.networkManager.Allocate()
if err != nil { if err != nil {
return err return err
@ -754,6 +767,9 @@ func (container *Container) allocateNetwork() error {
} }
func (container *Container) releaseNetwork() { func (container *Container) releaseNetwork() {
if container.Config.NetworkDisabled {
return
}
container.network.Release() container.network.Release()
container.network = nil container.network = nil
container.NetworkSettings = &NetworkSettings{} container.NetworkSettings = &NetworkSettings{}
@ -789,7 +805,9 @@ func (container *Container) monitor() {
} }
} }
utils.Debugf("Process finished") utils.Debugf("Process finished")
if container.runtime != nil && container.runtime.srv != nil {
container.runtime.srv.LogEvent("die", container.ShortID())
}
exitCode := -1 exitCode := -1
if container.cmd != nil { if container.cmd != nil {
exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()

View file

@ -1252,3 +1252,41 @@ func TestRestartWithVolumes(t *testing.T) {
t.Fatalf("Expected volume path: %s Actual path: %s", expected, actual) t.Fatalf("Expected volume path: %s Actual path: %s", expected, actual)
} }
} }
func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
config, hc, _, err := ParseRun([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil)
if err != nil {
t.Fatal(err)
}
c, err := NewBuilder(runtime).Create(config)
if err != nil {
t.Fatal(err)
}
stdout, err := c.StdoutPipe()
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(c)
if err := c.Start(hc); err != nil {
t.Fatal(err)
}
c.WaitTimeout(500 * time.Millisecond)
c.Wait()
output, err := ioutil.ReadAll(stdout)
if err != nil {
t.Fatal(err)
}
interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(string(output), -1)
if len(interfaces) != 1 {
t.Fatalf("Wrong interface count in test container: expected [1: lo], got [%s]", interfaces)
}
if interfaces[0] != "1: lo" {
t.Fatalf("Wrong interface in test container: expected [1: lo], got [%s]", interfaces)
}
}

View file

@ -28,7 +28,7 @@ func main() {
flDaemon := flag.Bool("d", false, "Daemon mode") flDaemon := flag.Bool("d", false, "Daemon mode")
flDebug := flag.Bool("D", false, "Debug mode") flDebug := flag.Bool("D", false, "Debug mode")
flAutoRestart := flag.Bool("r", false, "Restart previously running containers") flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge") bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")

View file

@ -44,6 +44,10 @@ What's new
**New!** List the processes running inside a container. **New!** List the processes running inside a container.
.. http:get:: /events:
**New!** Monitor docker's events via streaming or via polling
Builder (/build): Builder (/build):
- Simplify the upload of the build context - Simplify the upload of the build context

View file

@ -1059,6 +1059,36 @@ Create a new image from a container's changes
:statuscode 500: server error :statuscode 500: server error
Monitor Docker's events
***********************
.. http:get:: /events
Get events from docker, either in real time via streaming, or via polling (using `since`)
**Example request**:
.. sourcecode:: http
POST /events?since=1374067924
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"create","id":"dfdf82bd3881","time":1374067924}
{"status":"start","id":"dfdf82bd3881","time":1374067924}
{"status":"stop","id":"dfdf82bd3881","time":1374067966}
{"status":"destroy","id":"dfdf82bd3881","time":1374067970}
:query since: timestamp used for polling
:statuscode 200: no error
:statuscode 500: server error
3. Going further 3. Going further
================ ================

View file

@ -20,6 +20,7 @@
-h="": Container host name -h="": Container host name
-i=false: Keep stdin open even if not attached -i=false: Keep stdin open even if not attached
-m=0: Memory limit (in bytes) -m=0: Memory limit (in bytes)
-n=true: Enable networking for this container
-p=[]: Map a network port to the container -p=[]: Map a network port to the container
-t=false: Allocate a pseudo-tty -t=false: Allocate a pseudo-tty
-u="": Username or UID -u="": Username or UID

View file

@ -187,7 +187,7 @@ The copy obeys the following rules:
3.8 ENTRYPOINT 3.8 ENTRYPOINT
-------------- --------------
``ENTRYPOINT /bin/echo`` ``ENTRYPOINT ["/bin/echo"]``
The ``ENTRYPOINT`` instruction adds an entry command that will not be The ``ENTRYPOINT`` instruction adds an entry command that will not be
overwritten when arguments are passed to docker run, unlike the overwritten when arguments are passed to docker run, unlike the

View file

@ -13,6 +13,10 @@ lxc.utsname = {{.Id}}
{{end}} {{end}}
#lxc.aa_profile = unconfined #lxc.aa_profile = unconfined
{{if .Config.NetworkDisabled}}
# network is disabled (-n=false)
lxc.network.type = empty
{{else}}
# network configuration # network configuration
lxc.network.type = veth lxc.network.type = veth
lxc.network.flags = up lxc.network.flags = up
@ -20,6 +24,7 @@ lxc.network.link = {{.NetworkSettings.Bridge}}
lxc.network.name = eth0 lxc.network.name = eth0
lxc.network.mtu = 1500 lxc.network.mtu = 1500
lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}} lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}}
{{end}}
# root filesystem # root filesystem
{{$ROOTFS := .RootfsPath}} {{$ROOTFS := .RootfsPath}}

View file

@ -17,6 +17,7 @@ var NetworkBridgeIface string
const ( const (
DefaultNetworkBridge = "docker0" DefaultNetworkBridge = "docker0"
DisableNetworkBridge = "none"
portRangeStart = 49153 portRangeStart = 49153
portRangeEnd = 65535 portRangeEnd = 65535
) )
@ -472,10 +473,16 @@ type NetworkInterface struct {
manager *NetworkManager manager *NetworkManager
extPorts []*Nat extPorts []*Nat
disabled bool
} }
// Allocate an external TCP port and map it to the interface // Allocate an external TCP port and map it to the interface
func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
if iface.disabled {
return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME
}
nat, err := parseNat(spec) nat, err := parseNat(spec)
if err != nil { if err != nil {
return nil, err return nil, err
@ -571,6 +578,11 @@ func parseNat(spec string) (*Nat, error) {
// Release: Network cleanup - release all resources // Release: Network cleanup - release all resources
func (iface *NetworkInterface) Release() { func (iface *NetworkInterface) Release() {
if iface.disabled {
return
}
for _, nat := range iface.extPorts { for _, nat := range iface.extPorts {
utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend) utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend)
if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil { if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil {
@ -598,10 +610,17 @@ type NetworkManager struct {
tcpPortAllocator *PortAllocator tcpPortAllocator *PortAllocator
udpPortAllocator *PortAllocator udpPortAllocator *PortAllocator
portMapper *PortMapper portMapper *PortMapper
disabled bool
} }
// Allocate a network interface // Allocate a network interface
func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
if manager.disabled {
return &NetworkInterface{disabled: true}, nil
}
ip, err := manager.ipAllocator.Acquire() ip, err := manager.ipAllocator.Acquire()
if err != nil { if err != nil {
return nil, err return nil, err
@ -615,6 +634,14 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
} }
func newNetworkManager(bridgeIface string) (*NetworkManager, error) { func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
if bridgeIface == DisableNetworkBridge {
manager := &NetworkManager{
disabled: true,
}
return manager, nil
}
addr, err := getIfaceAddr(bridgeIface) addr, err := getIfaceAddr(bridgeIface)
if err != nil { if err != nil {
// If the iface is not found, try to create it // If the iface is not found, try to create it

View file

@ -17,12 +17,12 @@ import (
) )
const ( const (
unitTestImageName = "docker-test-image" unitTestImageName = "docker-test-image"
unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
unitTestNetworkBridge = "testdockbr0" unitTestNetworkBridge = "testdockbr0"
unitTestStoreBase = "/var/lib/docker/unit-tests" unitTestStoreBase = "/var/lib/docker/unit-tests"
testDaemonAddr = "127.0.0.1:4270" testDaemonAddr = "127.0.0.1:4270"
testDaemonProto = "tcp" testDaemonProto = "tcp"
) )
var globalRuntime *Runtime var globalRuntime *Runtime

View file

@ -19,6 +19,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"time"
) )
func (srv *Server) DockerVersion() APIVersion { func (srv *Server) DockerVersion() APIVersion {
@ -32,8 +33,9 @@ func (srv *Server) DockerVersion() APIVersion {
func (srv *Server) ContainerKill(name string) error { func (srv *Server) ContainerKill(name string) error {
if container := srv.runtime.Get(name); container != nil { if container := srv.runtime.Get(name); container != nil {
if err := container.Kill(); err != nil { if err := container.Kill(); err != nil {
return fmt.Errorf("Error restarting container %s: %s", name, err) return fmt.Errorf("Error killing container %s: %s", name, err)
} }
srv.LogEvent("kill", name)
} else { } else {
return fmt.Errorf("No such container: %s", name) return fmt.Errorf("No such container: %s", name)
} }
@ -52,6 +54,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
if _, err := io.Copy(out, data); err != nil { if _, err := io.Copy(out, data); err != nil {
return err return err
} }
srv.LogEvent("export", name)
return nil return nil
} }
return fmt.Errorf("No such container: %s", name) return fmt.Errorf("No such container: %s", name)
@ -209,13 +212,14 @@ func (srv *Server) DockerInfo() *APIInfo {
imgcount = len(images) imgcount = len(images)
} }
return &APIInfo{ return &APIInfo{
Containers: len(srv.runtime.List()), Containers: len(srv.runtime.List()),
Images: imgcount, Images: imgcount,
MemoryLimit: srv.runtime.capabilities.MemoryLimit, MemoryLimit: srv.runtime.capabilities.MemoryLimit,
SwapLimit: srv.runtime.capabilities.SwapLimit, SwapLimit: srv.runtime.capabilities.SwapLimit,
Debug: os.Getenv("DEBUG") != "", Debug: os.Getenv("DEBUG") != "",
NFd: utils.GetTotalUsedFds(), NFd: utils.GetTotalUsedFds(),
NGoroutines: runtime.NumGoroutine(), NGoroutines: runtime.NumGoroutine(),
NEventsListener: len(srv.events),
} }
} }
@ -810,6 +814,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
} }
return "", err return "", err
} }
srv.LogEvent("create", container.ShortID())
return container.ShortID(), nil return container.ShortID(), nil
} }
@ -818,6 +823,7 @@ func (srv *Server) ContainerRestart(name string, t int) error {
if err := container.Restart(t); err != nil { if err := container.Restart(t); err != nil {
return fmt.Errorf("Error restarting container %s: %s", name, err) return fmt.Errorf("Error restarting container %s: %s", name, err)
} }
srv.LogEvent("restart", name)
} else { } else {
return fmt.Errorf("No such container: %s", name) return fmt.Errorf("No such container: %s", name)
} }
@ -837,6 +843,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
if err := srv.runtime.Destroy(container); err != nil { if err := srv.runtime.Destroy(container); err != nil {
return fmt.Errorf("Error destroying container %s: %s", name, err) return fmt.Errorf("Error destroying container %s: %s", name, err)
} }
srv.LogEvent("destroy", name)
if removeVolume { if removeVolume {
// Retrieve all volumes from all remaining containers // Retrieve all volumes from all remaining containers
@ -903,6 +910,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
return err return err
} }
*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)}) *imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)})
srv.LogEvent("delete", utils.TruncateID(id))
return nil return nil
} }
return nil return nil
@ -946,6 +954,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro
} }
if tagDeleted { if tagDeleted {
imgs = append(imgs, APIRmi{Untagged: img.ShortID()}) imgs = append(imgs, APIRmi{Untagged: img.ShortID()})
srv.LogEvent("untag", img.ShortID())
} }
if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil { if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil {
@ -1018,6 +1027,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
if err := container.Start(hostConfig); err != nil { if err := container.Start(hostConfig); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err) return fmt.Errorf("Error starting container %s: %s", name, err)
} }
srv.LogEvent("start", name)
} else { } else {
return fmt.Errorf("No such container: %s", name) return fmt.Errorf("No such container: %s", name)
} }
@ -1029,6 +1039,7 @@ func (srv *Server) ContainerStop(name string, t int) error {
if err := container.Stop(t); err != nil { if err := container.Stop(t); err != nil {
return fmt.Errorf("Error stopping container %s: %s", name, err) return fmt.Errorf("Error stopping container %s: %s", name, err)
} }
srv.LogEvent("stop", name)
} else { } else {
return fmt.Errorf("No such container: %s", name) return fmt.Errorf("No such container: %s", name)
} }
@ -1162,15 +1173,31 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (
enableCors: enableCors, enableCors: enableCors,
pullingPool: make(map[string]struct{}), pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}),
events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
listeners: make(map[string]chan utils.JSONMessage),
} }
runtime.srv = srv runtime.srv = srv
return srv, nil return srv, nil
} }
func (srv *Server) LogEvent(action, id string) {
now := time.Now().Unix()
jm := utils.JSONMessage{Status: action, ID: id, Time: now}
srv.events = append(srv.events, jm)
for _, c := range srv.listeners {
select { // non blocking channel
case c <- jm:
default:
}
}
}
type Server struct { type Server struct {
sync.Mutex sync.Mutex
runtime *Runtime runtime *Runtime
enableCors bool enableCors bool
pullingPool map[string]struct{} pullingPool map[string]struct{}
pushingPool map[string]struct{} pushingPool map[string]struct{}
events []utils.JSONMessage
listeners map[string]chan utils.JSONMessage
} }

View file

@ -1,7 +1,9 @@
package docker package docker
import ( import (
"github.com/dotcloud/docker/utils"
"testing" "testing"
"time"
) )
func TestContainerTagImageDelete(t *testing.T) { func TestContainerTagImageDelete(t *testing.T) {
@ -163,3 +165,41 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) {
} }
} }
func TestLogEvent(t *testing.T) {
runtime := mkRuntime(t)
srv := &Server{
runtime: runtime,
events: make([]utils.JSONMessage, 0, 64),
listeners: make(map[string]chan utils.JSONMessage),
}
srv.LogEvent("fakeaction", "fakeid")
listener := make(chan utils.JSONMessage)
srv.Lock()
srv.listeners["test"] = listener
srv.Unlock()
srv.LogEvent("fakeaction2", "fakeid")
if len(srv.events) != 2 {
t.Fatalf("Expected 2 events, found %d", len(srv.events))
}
go func() {
time.Sleep(200 * time.Millisecond)
srv.LogEvent("fakeaction3", "fakeid")
time.Sleep(200 * time.Millisecond)
srv.LogEvent("fakeaction4", "fakeid")
}()
setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
for i := 2; i < 4; i++ {
event := <-listener
if event != srv.events[i] {
t.Fatalf("Event received it different than expected")
}
}
})
}

View file

@ -611,8 +611,27 @@ type JSONMessage struct {
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
Progress string `json:"progress,omitempty"` Progress string `json:"progress,omitempty"`
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
ID string `json:"id,omitempty"`
Time int64 `json:"time,omitempty"`
} }
func (jm *JSONMessage) Display(out io.Writer) (error) {
if jm.Time != 0 {
fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
}
if jm.Progress != "" {
fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
} else if jm.Error != "" {
return fmt.Errorf(jm.Error)
} else if jm.ID != "" {
fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status)
} else {
fmt.Fprintf(out, "%s\n", jm.Status)
}
return nil
}
type StreamFormatter struct { type StreamFormatter struct {
json bool json bool
used bool used bool