From 1cbdaebaa1c2326e57945333420d25d6f77011d5 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 4 Oct 2013 19:25:15 -0700 Subject: [PATCH 01/40] Add links for container relationships and introspection --- Dockerfile | 12 +- api.go | 105 +++- api_params.go | 18 +- api_test.go | 36 +- buildfile.go | 9 +- commands.go | 108 +++- config.go | 17 + container.go | 231 ++++++-- container_test.go | 70 +-- docker/docker.go | 50 +- docs/sources/commandline/cli.rst | 97 +++- docs/sources/examples/index.rst | 3 +- docs/sources/examples/linking_into_redis.rst | 146 +++++ gograph/MAINTAINERS | 1 + gograph/gograph.go | 455 ++++++++++++++++ gograph/gograph_test.go | 452 ++++++++++++++++ gograph/sort.go | 27 + gograph/sort_test.go | 29 + gograph/utils.go | 32 ++ hack/make.sh | 3 +- hack/make/binary | 6 +- hack/make/test | 2 +- iptables/MAINTAINERS | 1 + iptables/iptables.go | 105 ++++ iptables/iptables_test.go | 18 + links.go | 141 +++++ links_test.go | 104 ++++ network.go | 280 ++++------ network_test.go | 108 ---- proxy/MAINTAINERS | 1 + .../network_proxy_test.go | 2 +- proxy/proxy.go | 29 + proxy/tcp_proxy.go | 93 ++++ network_proxy.go => proxy/udp_proxy.go | 116 +--- runtime.go | 214 ++++++-- runtime_test.go | 340 ++++++++++-- server.go | 129 +++-- server_test.go | 20 +- sorter.go | 69 +++ sorter_test.go | 36 ++ utils.go | 153 +++++- utils/utils.go | 19 + utils/utils_test.go | 20 + utils_test.go | 186 +++++-- .../p/gosqlite/sqlite/sqlite.go | 404 ++++++++++++++ .../p/gosqlite/sqlite3/driver.go | 498 ++++++++++++++++++ 46 files changed, 4247 insertions(+), 748 deletions(-) create mode 100644 config.go create mode 100644 docs/sources/examples/linking_into_redis.rst create mode 100644 gograph/MAINTAINERS create mode 100644 gograph/gograph.go create mode 100644 gograph/gograph_test.go create mode 100644 gograph/sort.go create mode 100644 gograph/sort_test.go create mode 100644 gograph/utils.go create mode 100644 iptables/MAINTAINERS create mode 100644 iptables/iptables.go create mode 100644 iptables/iptables_test.go create mode 100644 links.go create mode 100644 links_test.go create mode 100644 proxy/MAINTAINERS rename network_proxy_test.go => proxy/network_proxy_test.go (99%) create mode 100644 proxy/proxy.go create mode 100644 proxy/tcp_proxy.go rename network_proxy.go => proxy/udp_proxy.go (55%) create mode 100644 vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go create mode 100644 vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go diff --git a/Dockerfile b/Dockerfile index 67963c836b..3c858c8989 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,15 +33,13 @@ run apt-get update run apt-get install -y -q curl run apt-get install -y -q git run apt-get install -y -q mercurial -run apt-get install -y -q build-essential +run apt-get install -y -q build-essential libsqlite3-dev -# Install Go from source (for eventual cross-compiling) -env CGO_ENABLED 0 -run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot -run cd /goroot/src && ./make.bash -env GOROOT /goroot -env PATH $PATH:/goroot/bin +# Install Go +run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C /usr/local -xz +env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor +run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std # Ubuntu stuff run apt-get install -y -q ruby1.9.3 rubygems libffi-dev diff --git a/api.go b/api.go index 6c1c47a001..cbaa5ab969 100644 --- a/api.go +++ b/api.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/utils" "github.com/gorilla/mux" "io" @@ -14,6 +15,7 @@ import ( "mime" "net" "net/http" + "net/url" "os" "os/exec" "regexp" @@ -154,7 +156,7 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r * } } } - + name = decodeName(name) if err := srv.ContainerKill(name, signal); err != nil { return err } @@ -167,6 +169,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } name := vars["name"] + name = decodeName(name) if err := srv.ContainerExport(name, w); err != nil { utils.Errorf("%s", err) @@ -534,16 +537,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r return err } - if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) { + if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns)) config.Dns = defaultDns } - id, err := srv.ContainerCreate(config) + id, warnings, err := srv.ContainerCreate(config) if err != nil { return err } out.ID = id + for _, warning := range warnings { + out.Warnings = append(out.Warnings, warning) + } if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") @@ -574,6 +580,7 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, return fmt.Errorf("Missing parameter") } name := vars["name"] + name = decodeName(name) if err := srv.ContainerRestart(name, t); err != nil { return err } @@ -589,12 +596,18 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht return fmt.Errorf("Missing parameter") } name := vars["name"] + name = decodeName(name) + removeVolume, err := getBoolParam(r.Form.Get("v")) if err != nil { return err } + removeLink, err := getBoolParam(r.Form.Get("link")) + if err != nil { + return err + } - if err := srv.ContainerDestroy(name, removeVolume); err != nil { + if err := srv.ContainerDestroy(name, removeVolume, removeLink); err != nil { return err } w.WriteHeader(http.StatusNoContent) @@ -640,7 +653,12 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r if vars == nil { return fmt.Errorf("Missing parameter") } + var err error name := vars["name"] + name = decodeName(name) + if err != nil { + return err + } if err := srv.ContainerStart(name, hostConfig); err != nil { return err } @@ -661,6 +679,7 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } name := vars["name"] + name = decodeName(name) if err := srv.ContainerStop(name, t); err != nil { return err @@ -674,6 +693,8 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } name := vars["name"] + name = decodeName(name) + status, err := srv.ContainerWait(name) if err != nil { return err @@ -733,6 +754,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } name := vars["name"] + name = decodeName(name) c, err := srv.ContainerInspect(name) if err != nil { @@ -805,6 +827,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } name := vars["name"] + name = decodeName(name) if _, err := srv.ContainerInspect(name); err != nil { return err @@ -827,6 +850,7 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } name := vars["name"] + name = decodeName(name) container, err := srv.ContainerInspect(name) if err != nil { @@ -994,7 +1018,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s if err != nil { version = APIVERSION } - if srv.enableCors { + if srv.runtime.config.EnableCors { writeCorsHeaders(w, r) } @@ -1010,6 +1034,75 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s } } +func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + + runtime := srv.runtime + all, err := getBoolParam(r.Form.Get("all")) + if err != nil { + return err + } + + out := []APILink{} + err = runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { + if container := runtime.Get(e.ID()); container != nil { + if !all && strings.Contains(p, container.ID) { + return nil + } + out = append(out, APILink{ + Path: p, + ContainerID: container.ID, + Image: runtime.repositories.ImageName(container.Image), + }) + } + return nil + }, -1) + + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, out) +} + +func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + values := make(map[string]string) + if matchesContentType(r.Header.Get("Content-Type"), "application/json") && r.Body != nil { + defer r.Body.Close() + + dec := json.NewDecoder(r.Body) + if err := dec.Decode(&values); err != nil { + return err + } + } else { + return fmt.Errorf("Invalid json body") + } + currentName := values["currentName"] + newName := values["newName"] + + if currentName == "" { + return fmt.Errorf("currentName cannot be empty") + } + if newName == "" { + return fmt.Errorf("newName cannot be empty") + } + + if err := srv.runtime.RenameLink(currentName, newName); err != nil { + return err + } + + return nil +} + +func decodeName(name string) string { + s, _ := url.QueryUnescape(name) + return s +} + func createRouter(srv *Server, logging bool) (*mux.Router, error) { r := mux.NewRouter() @@ -1030,6 +1123,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/containers/{name:.*}/json": getContainersByName, "/containers/{name:.*}/top": getContainersTop, "/containers/{name:.*}/attach/ws": wsContainersAttach, + "/containers/links": getContainersLinks, }, "POST": { "/auth": postAuth, @@ -1048,6 +1142,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/containers/{name:.*}/resize": postContainersResize, "/containers/{name:.*}/attach": postContainersAttach, "/containers/{name:.*}/copy": postContainersCopy, + "/containers/link": postContainerLink, }, "DELETE": { "/containers/{name:.*}": deleteContainers, diff --git a/api_params.go b/api_params.go index 5f1a338057..5242d02221 100644 --- a/api_params.go +++ b/api_params.go @@ -1,7 +1,5 @@ package docker -import "encoding/json" - type APIHistory struct { ID string `json:"Id"` Tags []string `json:",omitempty"` @@ -52,6 +50,7 @@ type APIContainers struct { Ports []APIPort SizeRw int64 SizeRootFs int64 + Names []string } func (self *APIContainers) ToLegacy() APIContainersOld { @@ -96,14 +95,7 @@ type APIPort struct { PrivatePort int64 PublicPort int64 Type string -} - -func (port *APIPort) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]interface{}{ - "PrivatePort": port.PrivatePort, - "PublicPort": port.PublicPort, - "Type": port.Type, - }) + IP string } type APIVersion struct { @@ -129,3 +121,9 @@ type APICopy struct { Resource string HostPath string } + +type APILink struct { + Path string + ContainerID string + Image string +} diff --git a/api_test.go b/api_test.go index 71b91c285e..94a19a1362 100644 --- a/api_test.go +++ b/api_test.go @@ -349,7 +349,7 @@ func TestGetContainersJSON(t *testing.T) { beginLen := runtime.containers.Len() - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, }) @@ -386,7 +386,7 @@ func TestGetContainersExport(t *testing.T) { srv := &Server{runtime: runtime} // Create a container and remove a file - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, @@ -436,7 +436,7 @@ func TestGetContainersChanges(t *testing.T) { srv := &Server{runtime: runtime} // Create a container and remove a file - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/rm", "/etc/passwd"}, @@ -479,7 +479,7 @@ func TestGetContainersTop(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "cat"}, @@ -561,7 +561,7 @@ func TestGetContainersByName(t *testing.T) { srv := &Server{runtime: runtime} // Create a container and remove a file - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, @@ -592,7 +592,7 @@ func TestPostCommit(t *testing.T) { srv := &Server{runtime: runtime} // Create a container and remove a file - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, @@ -686,7 +686,7 @@ func TestPostContainersKill(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, @@ -728,7 +728,7 @@ func TestPostContainersRestart(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/top"}, @@ -782,7 +782,7 @@ func TestPostContainersStart(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, @@ -834,7 +834,7 @@ func TestPostContainersStop(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/top"}, @@ -881,7 +881,7 @@ func TestPostContainersWait(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sleep", "1"}, @@ -923,7 +923,7 @@ func TestPostContainersAttach(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, @@ -1012,7 +1012,7 @@ func TestPostContainersAttachStderr(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, @@ -1104,7 +1104,7 @@ func TestDeleteContainers(t *testing.T) { srv := &Server{runtime: runtime} - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }) @@ -1142,7 +1142,8 @@ func TestOptionsRoute(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - srv := &Server{runtime: runtime, enableCors: true} + runtime.config.EnableCors = true + srv := &Server{runtime: runtime} r := httptest.NewRecorder() router, err := createRouter(srv, false) @@ -1165,7 +1166,8 @@ func TestGetEnabledCors(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - srv := &Server{runtime: runtime, enableCors: true} + runtime.config.EnableCors = true + srv := &Server{runtime: runtime} r := httptest.NewRecorder() @@ -1292,7 +1294,7 @@ func TestPostContainersCopy(t *testing.T) { srv := &Server{runtime: runtime} // Create a container and remove a file - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test.txt"}, diff --git a/buildfile.go b/buildfile.go index 6aae0469cf..a41443a965 100644 --- a/buildfile.go +++ b/buildfile.go @@ -187,6 +187,9 @@ func (b *buildFile) CmdCmd(args string) error { } func (b *buildFile) CmdExpose(args string) error { + if strings.Contains(args, ":") { + return fmt.Errorf("EXPOSE cannot be used to bind to a host ip or port") + } ports := strings.Split(args, " ") b.config.PortSpecs = append(ports, b.config.PortSpecs...) return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports)) @@ -332,7 +335,7 @@ func (b *buildFile) CmdAdd(args string) error { b.config.Image = b.image // Create the container and start it - container, err := b.runtime.Create(b.config) + container, _, err := b.runtime.Create(b.config) if err != nil { return err } @@ -367,7 +370,7 @@ func (b *buildFile) run() (string, error) { b.config.Image = b.image // Create the container and start it - c, err := b.runtime.Create(b.config) + c, _, err := b.runtime.Create(b.config) if err != nil { return "", err } @@ -430,7 +433,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { } } - container, err := b.runtime.Create(b.config) + container, _, err := b.runtime.Create(b.config) if err != nil { return err } diff --git a/commands.go b/commands.go index 56e15ab2f0..9a8ae57310 100644 --- a/commands.go +++ b/commands.go @@ -92,6 +92,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"kill", "Kill a running container"}, {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, + {"ls", "List links for containers"}, {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, {"ps", "List containers"}, {"pull", "Pull an image or a repository from the docker registry server"}, @@ -504,7 +505,8 @@ func (cli *DockerCli) CmdStop(args ...string) error { v.Set("t", strconv.Itoa(*nSeconds)) for _, name := range cmd.Args() { - _, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil) + encName := cleanName(name) + _, _, err := cli.call("POST", "/containers/"+encName+"/stop?"+v.Encode(), nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) } else { @@ -529,7 +531,8 @@ func (cli *DockerCli) CmdRestart(args ...string) error { v.Set("t", strconv.Itoa(*nSeconds)) for _, name := range cmd.Args() { - _, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil) + encName := cleanName(name) + _, _, err := cli.call("POST", "/containers/"+encName+"/restart?"+v.Encode(), nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) } else { @@ -605,7 +608,8 @@ func (cli *DockerCli) CmdStart(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := cli.call("POST", "/containers/"+name+"/start", nil) + encName := cleanName(name) + _, _, err := cli.call("POST", "/containers/"+encName+"/start", nil) if err != nil { if !*attach || !*openStdin { fmt.Fprintf(cli.err, "%s\n", err) @@ -811,6 +815,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error { func (cli *DockerCli) CmdRm(args ...string) error { cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers") v := cmd.Bool("v", false, "Remove the volumes associated to the container") + link := cmd.Bool("link", false, "Remove the specified link and not the underlying container") + if err := cmd.Parse(args); err != nil { return nil } @@ -822,8 +828,12 @@ func (cli *DockerCli) CmdRm(args ...string) error { if *v { val.Set("v", "1") } + if *link { + val.Set("link", "1") + } for _, name := range cmd.Args() { - _, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil) + encName := cleanName(name) + _, _, err := cli.call("DELETE", "/containers/"+encName+"?"+val.Encode(), nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) } else { @@ -845,7 +855,8 @@ func (cli *DockerCli) CmdKill(args ...string) error { } for _, name := range args { - _, _, err := cli.call("POST", "/containers/"+name+"/kill", nil) + encName := cleanName(name) + _, _, err := cli.call("POST", "/containers/"+encName+"/kill", nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) } else { @@ -1088,10 +1099,10 @@ func (cli *DockerCli) CmdImages(args ...string) error { func displayablePorts(ports []APIPort) string { result := []string{} for _, port := range ports { - if port.Type == "tcp" { - result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort)) + if port.IP == "" { + result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type)) } else { - result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type)) + result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)) } } sort.Strings(result) @@ -1144,7 +1155,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) if !*quiet { - fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS") + fmt.Fprint(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") if *size { fmt.Fprintln(w, "\tSIZE") } else { @@ -1153,11 +1164,16 @@ func (cli *DockerCli) CmdPs(args ...string) error { } for _, out := range outs { + for i := 0; i < len(out.Names); i++ { + out.Names[i] = utils.Trunc(out.Names[i], 10) + } + + names := strings.Join(out.Names, ",") if !*quiet { if *noTrunc { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports)) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names) } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports)) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names) } if *size { if out.SizeRootFs > 0 { @@ -1183,6 +1199,64 @@ func (cli *DockerCli) CmdPs(args ...string) error { return nil } +func (cli *DockerCli) CmdLs(args ...string) error { + cmd := Subcmd("ls", "", "List links for containers") + flAll := cmd.Bool("a", false, "Show all links") + + if err := cmd.Parse(args); err != nil { + return nil + } + v := url.Values{} + if *flAll { + v.Set("all", "1") + } + + body, _, err := cli.call("GET", "/containers/links?"+v.Encode(), nil) + if err != nil { + return err + } + var links []APILink + if err := json.Unmarshal(body, &links); err != nil { + return err + } + + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + fmt.Fprintf(w, "NAME\tID\tIMAGE") + fmt.Fprintf(w, "\n") + + sortLinks(links, func(i, j APILink) bool { + return len(i.Path) < len(j.Path) + }) + for _, link := range links { + fmt.Fprintf(w, "%s\t%s\t%s", link.Path, utils.TruncateID(link.ContainerID), link.Image) + fmt.Fprintf(w, "\n") + } + w.Flush() + + return nil +} + +func (cli *DockerCli) CmdLink(args ...string) error { + cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() != 2 { + cmd.Usage() + return nil + } + body := map[string]string{ + "currentName": cmd.Arg(0), + "newName": cmd.Arg(1), + } + + _, _, err := cli.call("POST", "/containers/link", body) + if err != nil { + return err + } + return nil +} + func (cli *DockerCli) CmdCommit(args ...string) error { cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") @@ -1300,8 +1374,9 @@ func (cli *DockerCli) CmdLogs(args ...string) error { cmd.Usage() return nil } + name := cleanName(cmd.Arg(0)) - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil { + if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil { return err } return nil @@ -1318,8 +1393,9 @@ func (cli *DockerCli) CmdAttach(args ...string) error { cmd.Usage() return nil } - - body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) + name := cmd.Arg(0) + name = cleanName(name) + body, _, err := cli.call("GET", "/containers/"+name+"/json", nil) if err != nil { return err } @@ -2028,6 +2104,10 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { return c.State.Running, c.State.ExitCode, nil } +func cleanName(name string) string { + return strings.Replace(name, "/", "%252F", -1) +} + func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { var ( isTerminal = false diff --git a/config.go b/config.go new file mode 100644 index 0000000000..d321790567 --- /dev/null +++ b/config.go @@ -0,0 +1,17 @@ +package docker + +import ( + "net" +) + +type DaemonConfig struct { + Pidfile string + GraphPath string + ProtoAddresses []string + AutoRestart bool + EnableCors bool + Dns []string + EnableIptables bool + BridgeIface string + DefaultIp net.IP +} diff --git a/container.go b/container.go index 54a51baad9..c6899d2bc5 100644 --- a/container.go +++ b/container.go @@ -59,6 +59,8 @@ type Container struct { // Store rw/ro in a separate structure to preserve reverse-compatibility on-disk. // Easier than migrating older container configs :) VolumesRW map[string]bool + + activeLinks map[string]*Link } type Config struct { @@ -71,7 +73,8 @@ type Config struct { AttachStdin bool AttachStdout bool AttachStderr bool - PortSpecs []string + PortSpecs []string // Deprecated - Can be in the format of 8080/tcp + ExposedPorts map[Port]struct{} 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. @@ -91,6 +94,8 @@ type HostConfig struct { Binds []string ContainerIDFile string LxcConf []KeyValuePair + PortBindings map[Port][]PortBinding + Links []string } type BindMap struct { @@ -113,6 +118,34 @@ type KeyValuePair struct { Value string } +type PortBinding struct { + HostIp string + HostPort string +} + +// 80/tcp +type Port string + +func (p Port) Proto() string { + return strings.Split(string(p), "/")[1] +} + +func (p Port) Port() string { + return strings.Split(string(p), "/")[0] +} + +func (p Port) Int() int { + i, err := parsePort(p.Port()) + if err != nil { + panic(err) + } + return i +} + +func NewPort(proto, port string) Port { + return Port(fmt.Sprintf("%s/%s", port, proto)) +} + func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) { cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container") if os.Getenv("TEST") != "" { @@ -142,8 +175,11 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") - var flPorts ListOpts - cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)") + var flPublish ListOpts + cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)") + + var flExpose ListOpts + cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host") var flEnv ListOpts cmd.Var(&flEnv, "e", "Set environment variables") @@ -162,6 +198,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, var flLxcOpts ListOpts cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + var flLinks ListOpts + cmd.Var(&flLinks, "link", "Add link to another container (containerid:alias)") + if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err } @@ -230,10 +269,28 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, hostname = parts[0] domainname = parts[1] } + + ports, portBindings, err := parsePortSpecs(flPublish) + if err != nil { + return nil, nil, cmd, err + } + + // Merge in exposed ports to the map of published ports + for _, e := range flExpose { + if strings.Contains(e, ":") { + return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e) + } + p := NewPort(splitProtoPort(e)) + if _, exists := ports[p]; !exists { + ports[p] = struct{}{} + } + } + config := &Config{ - Hostname: hostname, + Hostname: *flHostname, Domainname: domainname, - PortSpecs: flPorts, + PortSpecs: nil, // Deprecated + ExposedPorts: ports, User: *flUser, Tty: *flTty, NetworkDisabled: !*flNetwork, @@ -253,10 +310,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Privileged: *flPrivileged, WorkingDir: *flWorkingDir, } + hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, LxcConf: lxcConf, + PortBindings: portBindings, + Links: flLinks, } if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { @@ -271,36 +331,38 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, return config, hostConfig, cmd, nil } -type PortMapping map[string]string +type PortMapping map[string]string // Deprecated type NetworkSettings struct { IPAddress string IPPrefixLen int Gateway string Bridge string - PortMapping map[string]PortMapping + PortMapping map[string]PortMapping // Deprecated + Ports map[Port][]PortBinding } -// returns a more easy to process description of the port mapping defined in the settings func (settings *NetworkSettings) PortMappingAPI() []APIPort { var mapping []APIPort - for private, public := range settings.PortMapping["Tcp"] { - pubint, _ := strconv.ParseInt(public, 0, 0) - privint, _ := strconv.ParseInt(private, 0, 0) - mapping = append(mapping, APIPort{ - PrivatePort: privint, - PublicPort: pubint, - Type: "tcp", - }) - } - for private, public := range settings.PortMapping["Udp"] { - pubint, _ := strconv.ParseInt(public, 0, 0) - privint, _ := strconv.ParseInt(private, 0, 0) - mapping = append(mapping, APIPort{ - PrivatePort: privint, - PublicPort: pubint, - Type: "udp", - }) + for port, bindings := range settings.Ports { + p, _ := parsePort(port.Port()) + if len(bindings) == 0 { + mapping = append(mapping, APIPort{ + PublicPort: int64(p), + Type: port.Proto(), + }) + continue + } + for _, binding := range bindings { + p, _ := parsePort(port.Port()) + h, _ := parsePort(binding.HostPort) + mapping = append(mapping, APIPort{ + PrivatePort: int64(p), + PublicPort: int64(h), + Type: port.Proto(), + IP: binding.HostIp, + }) + } } return mapping } @@ -602,7 +664,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) { if container.runtime.networkManager.disabled { container.Config.NetworkDisabled = true } else { - if err := container.allocateNetwork(); err != nil { + if err := container.allocateNetwork(hostConfig); err != nil { return err } } @@ -792,6 +854,46 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) { "-e", "container=lxc", "-e", "HOSTNAME="+container.Config.Hostname, ) + + // Init any links between the parent and children + runtime := container.runtime + + children, err := runtime.Children(fmt.Sprintf("/%s", container.ID)) + if err != nil { + return err + } + + if len(children) > 0 { + container.activeLinks = make(map[string]*Link, len(children)) + + // If we encounter an error make sure that we rollback any network + // config and ip table changes + rollback := func() { + for _, link := range container.activeLinks { + link.Disable() + } + container.activeLinks = nil + } + + for p, child := range children { + link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface) + if err != nil { + rollback() + return err + } + + container.activeLinks[link.Alias()] = link + if err := link.Enable(); err != nil { + rollback() + return err + } + + for _, envVar := range link.ToEnv() { + params = append(params, "-e", envVar) + } + } + } + if container.Config.WorkingDir != "" { workingDir := path.Clean(container.Config.WorkingDir) utils.Debugf("[working dir] working dir is %s", workingDir) @@ -925,7 +1027,7 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) { return utils.NewBufReader(reader), nil } -func (container *Container) allocateNetwork() error { +func (container *Container) allocateNetwork(hostConfig *HostConfig) error { if container.Config.NetworkDisabled { return nil } @@ -952,36 +1054,59 @@ func (container *Container) allocateNetwork() error { } } - var portSpecs []string - if !container.State.Ghost { - portSpecs = container.Config.PortSpecs - } else { - for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend)) + if container.Config.PortSpecs != nil { + utils.Debugf("Migrating port mappings for container: %s", strings.Join(container.Config.PortSpecs, ", ")) + if err := migratePortMappings(container.Config); err != nil { + return err } - for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend)) + container.Config.PortSpecs = nil + } + + portSpecs := make(map[Port]struct{}) + bindings := make(map[Port][]PortBinding) + + if !container.State.Ghost { + if container.Config.ExposedPorts != nil { + portSpecs = container.Config.ExposedPorts + } + if hostConfig.PortBindings != nil { + bindings = hostConfig.PortBindings + } + } else { + if container.NetworkSettings.Ports != nil { + for port, binding := range container.NetworkSettings.Ports { + portSpecs[port] = struct{}{} + bindings[port] = binding + } } } - container.NetworkSettings.PortMapping = make(map[string]PortMapping) - container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping) - container.NetworkSettings.PortMapping["Udp"] = make(PortMapping) - for _, spec := range portSpecs { - nat, err := iface.AllocatePort(spec) - if err != nil { - iface.Release() - return err + container.NetworkSettings.PortMapping = nil + + for port := range portSpecs { + binding := bindings[port] + for i := 0; i < len(binding); i++ { + b := binding[i] + nat, err := iface.AllocatePort(port, b) + if err != nil { + iface.Release() + return err + } + utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort) + binding[i] = nat.Binding } - proto := strings.Title(nat.Proto) - backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend) - container.NetworkSettings.PortMapping[proto][backend] = frontend + bindings[port] = binding } + container.SaveHostConfig(hostConfig) + + container.NetworkSettings.Ports = bindings 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() + return nil } @@ -1064,6 +1189,14 @@ func (container *Container) monitor(hostConfig *HostConfig) { func (container *Container) cleanup() { container.releaseNetwork() + + // Disable all active links + if container.activeLinks != nil { + for _, link := range container.activeLinks { + link.Disable() + } + } + if container.Config.OpenStdin { if err := container.stdin.Close(); err != nil { utils.Errorf("%s: Error close stdin: %s", container.ID, err) @@ -1345,3 +1478,9 @@ func (container *Container) Copy(resource string) (Archive, error) { } return TarFilter(basePath, Uncompressed, filter) } + +// Returns true if the container exposes a certain port +func (container *Container) Exposes(p Port) bool { + _, exists := container.Config.ExposedPorts[p] + return exists +} diff --git a/container_test.go b/container_test.go index ea65d0fa77..b36898e85b 100644 --- a/container_test.go +++ b/container_test.go @@ -18,7 +18,7 @@ import ( func TestIDFormat(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container1, err := runtime.Create( + container1, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "echo hello world"}, @@ -388,7 +388,7 @@ func TestRun(t *testing.T) { func TestOutput(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, @@ -411,7 +411,7 @@ func TestKillDifferentUser(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, OpenStdin: true, @@ -471,7 +471,7 @@ func TestCreateVolume(t *testing.T) { if err != nil { t.Fatal(err) } - c, err := runtime.Create(config) + c, _, err := runtime.Create(config) if err != nil { t.Fatal(err) } @@ -486,7 +486,7 @@ func TestCreateVolume(t *testing.T) { func TestKill(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -530,7 +530,7 @@ func TestExitCode(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - trueContainer, err := runtime.Create(&Config{ + trueContainer, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true", ""}, }) @@ -545,7 +545,7 @@ func TestExitCode(t *testing.T) { t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode) } - falseContainer, err := runtime.Create(&Config{ + falseContainer, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/false", ""}, }) @@ -564,7 +564,7 @@ func TestExitCode(t *testing.T) { func TestRestart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, }, @@ -594,7 +594,7 @@ func TestRestart(t *testing.T) { func TestRestartStdin(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -672,7 +672,7 @@ func TestUser(t *testing.T) { defer nuke(runtime) // Default user must be root - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, }, @@ -690,7 +690,7 @@ func TestUser(t *testing.T) { } // Set a username - container, err = runtime.Create(&Config{ + container, _, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -710,7 +710,7 @@ func TestUser(t *testing.T) { } // Set a UID - container, err = runtime.Create(&Config{ + container, _, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -730,7 +730,7 @@ func TestUser(t *testing.T) { } // Set a different user by uid - container, err = runtime.Create(&Config{ + container, _, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -752,7 +752,7 @@ func TestUser(t *testing.T) { } // Set a different user by username - container, err = runtime.Create(&Config{ + container, _, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -772,7 +772,7 @@ func TestUser(t *testing.T) { } // Test an wrong username - container, err = runtime.Create(&Config{ + container, _, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -793,7 +793,7 @@ func TestMultipleContainers(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container1, err := runtime.Create(&Config{ + container1, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -803,7 +803,7 @@ func TestMultipleContainers(t *testing.T) { } defer runtime.Destroy(container1) - container2, err := runtime.Create(&Config{ + container2, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -847,7 +847,7 @@ func TestMultipleContainers(t *testing.T) { func TestStdin(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -892,7 +892,7 @@ func TestStdin(t *testing.T) { func TestTty(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -937,7 +937,7 @@ func TestTty(t *testing.T) { func TestEnv(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"env"}, }, @@ -986,7 +986,7 @@ func TestEnv(t *testing.T) { func TestEntrypoint(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Entrypoint: []string{"/bin/echo"}, @@ -1009,7 +1009,7 @@ func TestEntrypoint(t *testing.T) { func TestEntrypointNoCmd(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Entrypoint: []string{"/bin/echo", "foobar"}, @@ -1060,7 +1060,7 @@ func TestLXCConfig(t *testing.T) { cpuMin := 100 cpuMax := 10000 cpu := cpuMin + rand.Intn(cpuMax-cpuMin) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true"}, @@ -1084,7 +1084,7 @@ func TestLXCConfig(t *testing.T) { func TestCustomLxcConfig(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true"}, @@ -1115,7 +1115,7 @@ func BenchmarkRunSequencial(b *testing.B) { runtime := mkRuntime(b) defer nuke(runtime) for i := 0; i < b.N; i++ { - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, @@ -1147,7 +1147,7 @@ func BenchmarkRunParallel(b *testing.B) { complete := make(chan error) tasks = append(tasks, complete) go func(i int, complete chan error) { - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, @@ -1297,7 +1297,7 @@ func TestBindMounts(t *testing.T) { func TestVolumesFromReadonlyMount(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, @@ -1316,7 +1316,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) { t.Fail() } - container2, err := runtime.Create( + container2, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, @@ -1352,7 +1352,7 @@ func TestRestartWithVolumes(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1395,7 +1395,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1422,7 +1422,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { t.Fail() } - container2, err := runtime.Create( + container2, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat", "/test/foo"}, @@ -1463,7 +1463,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { if err != nil { t.Fatal(err) } - c, err := runtime.Create(config) + c, _, err := runtime.Create(config) if err != nil { t.Fatal(err) } @@ -1529,7 +1529,7 @@ func TestMultipleVolumesFrom(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1556,7 +1556,7 @@ func TestMultipleVolumesFrom(t *testing.T) { t.Fail() } - container2, err := runtime.Create( + container2, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"}, @@ -1577,7 +1577,7 @@ func TestMultipleVolumesFrom(t *testing.T) { t.Fatal(err) } - container3, err := runtime.Create( + container3, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, diff --git a/docker/docker.go b/docker/docker.go index 71b0770475..fbde547231 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -7,6 +7,7 @@ import ( "github.com/dotcloud/docker/utils" "io/ioutil" "log" + "net" "os" "os/signal" "strconv" @@ -37,7 +38,11 @@ func main() { flDns := flag.String("dns", "", "Set custom dns servers") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") + flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker") + flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports") + flag.Parse() + if *flVersion { showVersion() return @@ -54,10 +59,9 @@ func main() { } } + bridge := docker.DefaultNetworkBridge if *bridgeName != "" { - docker.NetworkBridgeIface = *bridgeName - } else { - docker.NetworkBridgeIface = docker.DefaultNetworkBridge + bridge = *bridgeName } if *flDebug { os.Setenv("DEBUG", "1") @@ -69,7 +73,25 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { + var dns []string + if *flDns != "" { + dns = []string{*flDns} + } + + ip := net.ParseIP(*flDefaultIp) + + config := &docker.DaemonConfig{ + Pidfile: *pidfile, + GraphPath: *flGraphPath, + AutoRestart: *flAutoRestart, + EnableCors: *flEnableCors, + Dns: dns, + EnableIptables: *flEnableIptables, + BridgeIface: bridge, + ProtoAddresses: flHosts, + DefaultIp: ip, + } + if err := daemon(config); err != nil { log.Fatal(err) } } else { @@ -117,30 +139,26 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { - if err := createPidFile(pidfile); err != nil { +func daemon(config *docker.DaemonConfig) error { + if err := createPidFile(config.Pidfile); err != nil { log.Fatal(err) } - defer removePidFile(pidfile) + defer removePidFile(config.Pidfile) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) go func() { sig := <-c log.Printf("Received signal '%v', exiting\n", sig) - removePidFile(pidfile) + removePidFile(config.Pidfile) os.Exit(0) }() - var dns []string - if flDns != "" { - dns = []string{flDns} - } - server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns) + server, err := docker.NewServer(config) if err != nil { return err } - chErrors := make(chan error, len(protoAddrs)) - for _, protoAddr := range protoAddrs { + chErrors := make(chan error, len(config.ProtoAddresses)) + for _, protoAddr := range config.ProtoAddresses { protoAddrParts := strings.SplitN(protoAddr, "://", 2) if protoAddrParts[0] == "unix" { syscall.Unlink(protoAddrParts[1]) @@ -155,7 +173,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true) }() } - for i := 0; i < len(protoAddrs); i += 1 { + for i := 0; i < len(config.ProtoAddresses); i += 1 { err := <-chErrors if err != nil { return err diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index cc0e46c14c..4ced41894a 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -96,8 +96,8 @@ Examples: .. _cli_build_examples: -Examples -~~~~~~~~ +Examples: +~~~~~~~~~ .. code-block:: bash @@ -403,6 +403,33 @@ Insert file from github Kill a running container +.. _cli_link: + +``link`` +-------- + +:: + + Usage: docker link CURRENT_NAME NEW_NAME + + Link a container to a new name. + + +Examples: +~~~~~~~~~ + +.. code-block:: bash + + $ docker link /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc /redis + $ docker ls + NAME ID IMAGE + /redis 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest + /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest + + +This will create a new link for the existing name ``/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc`` +with the new name ``/redis`` so that we can new reference the same container under the new name ``/redis``. + .. _cli_login: ``login`` @@ -430,7 +457,6 @@ Insert file from github ``logs`` -------- - :: Usage: docker logs [OPTIONS] CONTAINER @@ -510,6 +536,29 @@ Insert file from github Usage: docker rm [OPTIONS] CONTAINER Remove one or more containers + -link="": Remove the link instead of the actual container + + +Examples: +~~~~~~~~~ + +.. code-block:: bash + + $ docker rm /redis + /redis + + +This will remove the container referenced under the link ``/redis``. + + +.. code-block:: bash + + $ docker rm -link /webapp/redis + /webapp/redis + + +This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all +network communication. .. _cli_rmi: @@ -533,7 +582,7 @@ Insert file from github Run a command in a new container - -a=map[]: Attach to stdin, stdout or stderr. + -a=map[]: Attach to stdin, stdout or stderr -c=0: CPU shares (relative weight) -cidfile="": Write the container ID to the file -d=false: Detached mode: Run container in the background, print new container id @@ -549,14 +598,16 @@ Insert file from github -u="": Username or UID -dns=[]: Set custom dns servers for the container -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume. - -volumes-from="": Mount all volumes from the given container. - -entrypoint="": Overwrite the default entrypoint set by the image. + -volumes-from="": Mount all volumes from the given container + -entrypoint="": Overwrite the default entrypoint set by the image -w="": Working directory inside the container -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -sig-proxy=false: Proxify all received signal to the process (even in non-tty mode) + -expose=[]: Expose a port from the container without publishing it to your host + -link="": Add link to another container (containerid:alias) Examples -~~~~~~~~ +-------- .. code-block:: bash @@ -604,6 +655,38 @@ working directory, by changing into the directory to the value returned by ``pwd``. So this combination executes the command using the container, but inside the current working directory. +.. code-block:: bash + + docker run -p 127.0.0.0::80 ubuntu bash + +This the ``-p`` flag now allows you to bind a port to a specific +interface of the host machine. In this example port ``80`` of the +container will have a dynamically allocated port bound to 127.0.0.1 +of the host. + +.. code-block:: bash + + docker run -p 127.0.0.1:80:80 ubuntu bash + +This will bind port ``80`` of the container to port ``80`` on 127.0.0.1 of your +host machine. + +.. code-block:: bash + + docker run -expose 80 ubuntu bash + +This will expose port ``80`` of the container for use within a link +without publishing the port to the host system's interfaces. + +.. code-block:: bash + + docker run -link /redis:redis ubuntu bash + +The ``-link`` flag will link the container named ``/redis`` into the +newly created container with the alias ``redis``. The new container +can access the network and environment of the redis container via +environment variables. + .. _cli_search: ``search`` diff --git a/docs/sources/examples/index.rst b/docs/sources/examples/index.rst index 01251680b6..74a7e7c406 100644 --- a/docs/sources/examples/index.rst +++ b/docs/sources/examples/index.rst @@ -1,6 +1,6 @@ :title: Docker Examples :description: Examples on how to use Docker -:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql +:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql, link .. _example_list: @@ -24,3 +24,4 @@ to more substantial services like you might find in production. postgresql_service mongodb running_riak_service + linking_into_redis diff --git a/docs/sources/examples/linking_into_redis.rst b/docs/sources/examples/linking_into_redis.rst new file mode 100644 index 0000000000..6c9b7a4ffc --- /dev/null +++ b/docs/sources/examples/linking_into_redis.rst @@ -0,0 +1,146 @@ +:title: Linking to an Redis container +:description: Running redis linked into your web app +:keywords: docker, example, networking, redis, link + +.. _linking_redis: + +Linking Redis +============= + +.. include:: example_header.inc + +Building a redis container to link as a child of our web application. + +Building the redis container +---------------------------- + +We will use a pre-build version of redis from the index under +the name ``crosbymichael/redis``. If you are interested in the +Dockerfile that was used to build this container here it is. + +.. code-block:: bash + + # Build redis from source + # Make sure you have the redis source code checked out in + # the same directory as this Dockerfile + FROM ubuntu + + RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list + RUN apt-get update + RUN apt-get upgrade -y + + RUN apt-get install -y gcc make g++ build-essential libc6-dev tcl + + ADD . /redis + + RUN (cd /redis && make) + RUN (cd /redis && make test) + + RUN mkdir -p /redis-data + VOLUME ["/redis-data"] + EXPOSE 6379 + + ENTRYPOINT ["/redis/src/redis-server"] + CMD ["--dir", "/redis-data"] + + +We need to ``EXPOSE`` the default port of 6379 so that our link knows what ports +to connect to our redis container on. If you do not expose any ports for the +image then docker will not be able to establish the link between containers. + + +Run the redis container +----------------------- + +.. code-block:: bash + + docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass=docker + +This will run our redis container using the default port of 6379 and using +as password to secure our service. Next we will link the redis container to +a new name using ``docker link`` and ``docker ls``. + + +Linking an existing container +----------------------------- + +.. code-block:: bash + + docker ls + + NAME ID IMAGE + /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest + + +Docker will automatically create an initial link with the container's id but +because the is long and not very user friendly we can link the container with +a new name. + +.. code-block:: bash + + docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis + + docker ls + + NAME ID IMAGE + /redis 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest + /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest + +Now we can reference our running redis service using the friendly name ``/redis``. +We can issue all the commands that you would expect; start, stop, attach, using the new name. + +Linking redis as a child +------------------------ + +Next we can start a new web application that has a dependency on redis and apply a link +to connect both containers. If you noticed when running our redis service we did not use +the ``-p`` option to publish the redis port to the host system. Redis exposed port 6379 +but we did not publish the port. This allows docker to prevent all network traffic to +the redis container except when explicitly specified within a link. This is a big win +for security. + + +Now lets start our web application with a link into redis. + +.. code-block:: bash + + docker run -t -i -link /redis:db ubuntu bash + + root@4c01db0b339c:/# env + + HOSTNAME=4c01db0b339c + DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db + TERM=xterm + DB_PORT=tcp://172.17.0.8:6379 + DB_PORT_6379_TCP=tcp://172.17.0.8:6379 + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + PWD=/ + DB_ENV_PASSWORD=dockerpass + SHLVL=1 + HOME=/ + container=lxc + _=/usr/bin/env + root@4c01db0b339c:/# + + +When we inspect the environment of the linked container we can see a few extra environment +variables have been added. When you specified ``-link /redis:db`` you are telling docker +to link the container named ``/redis`` into this new container with the alias ``db``. +Environment variables are prefixed with the alias so that the parent container can access +network and environment information from the child. + +.. code-block:: bash + + # The name of the child container + DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db + # The default protocol, ip, and port of the service running in the container + DB_PORT=tcp://172.17.0.8:6379 + # A specific protocol, ip, and port of various services + DB_PORT_6379_TCP=tcp://172.17.0.8:6379 + # Get environment variables of the container + DB_ENV_PASSWORD=dockerpass + + +Accessing the network information along with the environment of the child container allows +us to easily connect to the redis service on the specific ip and port and use the password +specified in the environment. diff --git a/gograph/MAINTAINERS b/gograph/MAINTAINERS new file mode 100644 index 0000000000..1e998f8ac1 --- /dev/null +++ b/gograph/MAINTAINERS @@ -0,0 +1 @@ +Michael Crosby (@crosbymichael) diff --git a/gograph/gograph.go b/gograph/gograph.go new file mode 100644 index 0000000000..876bd6b58e --- /dev/null +++ b/gograph/gograph.go @@ -0,0 +1,455 @@ +package gograph + +import ( + _ "code.google.com/p/gosqlite/sqlite3" + "database/sql" + "fmt" + "os" + "path" +) + +const ( + createEntityTable = ` + CREATE TABLE IF NOT EXISTS entity ( + id text NOT NULL PRIMARY KEY + );` + + createEdgeTable = ` + CREATE TABLE IF NOT EXISTS edge ( + "entity_id" text NOT NULL, + "parent_id" text NULL, + "name" text NOT NULL, + CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"), + CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id") + ); + + CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name); + ` +) + +// Entity with a unique id +type Entity struct { + id string +} + +// An Edge connects two entities together +type Edge struct { + EntityID string + Name string + ParentID string +} + +type Entities map[string]*Entity +type Edges []*Edge + +type WalkFunc func(fullPath string, entity *Entity) error + +// Graph database for storing entities and their relationships +type Database struct { + dbPath string +} + +// Create a new graph database initialized with a root entity +func NewDatabase(dbPath string) (*Database, error) { + db := &Database{dbPath} + if _, err := os.Stat(dbPath); err == nil { + return db, nil + } + conn, err := db.openConn() + if err != nil { + return nil, err + } + defer conn.Close() + + if _, err := conn.Exec(createEntityTable); err != nil { + return nil, err + } + if _, err := conn.Exec(createEdgeTable); err != nil { + return nil, err + } + + rollback := func() { + conn.Exec("ROLLBACK") + } + + // Create root entities + if _, err := conn.Exec("BEGIN"); err != nil { + return nil, err + } + if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil { + rollback() + return nil, err + } + + if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil { + rollback() + return nil, err + } + + if _, err := conn.Exec("COMMIT"); err != nil { + return nil, err + } + return db, nil +} + +// Set the entity id for a given path +func (db *Database) Set(fullPath, id string) (*Entity, error) { + conn, err := db.openConn() + if err != nil { + return nil, err + } + defer conn.Close() + rollback := func() { + conn.Exec("ROLLBACK") + } + if _, err := conn.Exec("BEGIN"); err != nil { + return nil, err + } + var entityId string + if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil { + if err == sql.ErrNoRows { + if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil { + rollback() + return nil, err + } + } else { + rollback() + return nil, err + } + } + e := &Entity{id} + + parentPath, name := splitPath(fullPath) + if err := db.setEdge(conn, parentPath, name, e); err != nil { + rollback() + return nil, err + } + + if _, err := conn.Exec("COMMIT"); err != nil { + return nil, err + } + return e, nil +} + +func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error { + parent, err := db.get(conn, parentPath) + if err != nil { + return err + } + if parent.id == e.id { + return fmt.Errorf("Cannot set self as child") + } + + if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil { + return err + } + return nil +} + +// Return the root "/" entity for the database +func (db *Database) RootEntity() *Entity { + return &Entity{ + id: "0", + } +} + +// Return the entity for a given path +func (db *Database) Get(name string) *Entity { + conn, err := db.openConn() + if err != nil { + return nil + } + e, err := db.get(conn, name) + if err != nil { + return nil + } + return e +} + +func (db *Database) get(conn *sql.DB, name string) (*Entity, error) { + e := db.RootEntity() + // We always know the root name so return it if + // it is requested + if name == "/" { + return e, nil + } + + parts := split(name) + for i := 1; i < len(parts); i++ { + p := parts[i] + + next := db.child(conn, e, p) + if next == nil { + return nil, fmt.Errorf("Cannot find child") + } + e = next + } + return e, nil + +} + +// List all entities by from the name +// The key will be the full path of the entity +func (db *Database) List(name string, depth int) Entities { + out := Entities{} + conn, err := db.openConn() + if err != nil { + return out + } + defer conn.Close() + + for c := range db.children(conn, name, depth) { + out[c.FullPath] = c.Entity + } + return out +} + +func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { + conn, err := db.openConn() + if err != nil { + return err + } + defer conn.Close() + + for c := range db.children(conn, name, depth) { + if err := walkFunc(c.FullPath, c.Entity); err != nil { + return err + } + } + return nil +} + +// Return the refrence count for a specified id +func (db *Database) Refs(id string) int { + conn, err := db.openConn() + if err != nil { + return -1 + } + defer conn.Close() + + var count int + if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil { + return 0 + } + return count +} + +// Return all the id's path references +func (db *Database) RefPaths(id string) Edges { + refs := Edges{} + conn, err := db.openConn() + if err != nil { + return refs + } + defer conn.Close() + + rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id) + if err != nil { + return refs + } + defer rows.Close() + + for rows.Next() { + var name string + var parentId string + if err := rows.Scan(&name, &parentId); err != nil { + return refs + } + refs = append(refs, &Edge{ + EntityID: id, + Name: name, + ParentID: parentId, + }) + } + return refs +} + +// Delete the reference to an entity at a given path +func (db *Database) Delete(name string) error { + if name == "/" { + return fmt.Errorf("Cannot delete root entity") + } + conn, err := db.openConn() + if err != nil { + return err + } + defer conn.Close() + + parentPath, n := splitPath(name) + parent, err := db.get(conn, parentPath) + if err != nil { + return err + } + + if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil { + return err + } + return nil +} + +// Remove the entity with the specified id +// Walk the graph to make sure all references to the entity +// are removed and return the number of references removed +func (db *Database) Purge(id string) (int, error) { + conn, err := db.openConn() + if err != nil { + return -1, err + } + defer conn.Close() + + rollback := func() { + conn.Exec("ROLLBACK") + } + + if _, err := conn.Exec("BEGIN"); err != nil { + return -1, err + } + + // Delete all edges + rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id) + if err != nil { + rollback() + return -1, err + } + + changes, err := rows.RowsAffected() + if err != nil { + return -1, err + } + + // Delete entity + if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil { + rollback() + return -1, err + } + + if _, err := conn.Exec("COMMIT"); err != nil { + return -1, err + } + return int(changes), nil +} + +// Rename an edge for a given path +func (db *Database) Rename(currentName, newName string) error { + parentPath, name := splitPath(currentName) + newParentPath, newEdgeName := splitPath(newName) + + if parentPath != newParentPath { + return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath) + } + + conn, err := db.openConn() + if err != nil { + return err + } + defer conn.Close() + + parent, err := db.get(conn, parentPath) + if err != nil { + return err + } + + rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name) + if err != nil { + return err + } + i, err := rows.RowsAffected() + if err != nil { + return err + } + if i == 0 { + return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name) + } + return nil +} + +type WalkMeta struct { + Parent *Entity + Entity *Entity + FullPath string + Edge *Edge +} + +func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta { + out := make(chan WalkMeta) + e, err := db.get(conn, name) + if err != nil { + close(out) + return out + } + + go func() { + rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) + if err != nil { + close(out) + } + defer rows.Close() + + for rows.Next() { + var entityId, entityName string + if err := rows.Scan(&entityId, &entityName); err != nil { + // Log error + continue + } + child := &Entity{entityId} + edge := &Edge{ + ParentID: e.id, + Name: entityName, + EntityID: child.id, + } + + meta := WalkMeta{ + Parent: e, + Entity: child, + FullPath: path.Join(name, edge.Name), + Edge: edge, + } + + out <- meta + if depth == 0 { + continue + } + nDepth := depth + if depth != -1 { + nDepth -= 1 + } + sc := db.children(conn, meta.FullPath, nDepth) + for c := range sc { + out <- c + } + } + close(out) + }() + return out +} + +// Return the entity based on the parent path and name +func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity { + var id string + if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil { + return nil + } + return &Entity{id} +} + +func (db *Database) openConn() (*sql.DB, error) { + return sql.Open("sqlite3", db.dbPath) +} + +// Return the id used to reference this entity +func (e *Entity) ID() string { + return e.id +} + +// Return the paths sorted by depth +func (e Entities) Paths() []string { + out := make([]string, len(e)) + var i int + for k := range e { + out[i] = k + i++ + } + sortByDepth(out) + + return out +} diff --git a/gograph/gograph_test.go b/gograph/gograph_test.go new file mode 100644 index 0000000000..519b199699 --- /dev/null +++ b/gograph/gograph_test.go @@ -0,0 +1,452 @@ +package gograph + +import ( + "os" + "path" + "strconv" + "testing" +) + +func newTestDb(t *testing.T) *Database { + db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db")) + if err != nil { + t.Fatal(err) + } + return db +} + +func destroyTestDb(db *Database) { + os.Remove(db.dbPath) +} + +func TestNewDatabase(t *testing.T) { + db := newTestDb(t) + if db == nil { + t.Fatal("Datbase should not be nil") + } + defer destroyTestDb(db) +} + +func TestCreateRootEnity(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + root := db.RootEntity() + if root == nil { + t.Fatal("Root entity should not be nil") + } +} + +func TestGetRootEntity(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + e := db.Get("/") + if e == nil { + t.Fatal("Entity should not be nil") + } + if e.ID() != "0" { + t.Fatalf("Enity id should be 0, got %s", e.ID()) + } +} + +func TestSetEntityWithDifferentName(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + db.Set("/test", "1") + if _, err := db.Set("/other", "1"); err != nil { + t.Fatal(err) + } +} + +func TestCreateChild(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + child, err := db.Set("/db", "1") + if err != nil { + t.Fatal(err) + } + if child == nil { + t.Fatal("Child should not be nil") + } + if child.ID() != "1" { + t.Fail() + } +} + +func TestListAllRootChildren(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + for i := 1; i < 6; i++ { + a := strconv.Itoa(i) + if _, err := db.Set("/"+a, a); err != nil { + t.Fatal(err) + } + } + entries := db.List("/", -1) + if len(entries) != 5 { + t.Fatalf("Expect 5 entries for / got %d", len(entries)) + } +} + +func TestListAllSubChildren(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + _, err := db.Set("/webapp", "1") + if err != nil { + t.Fatal(err) + } + child2, err := db.Set("/db", "2") + if err != nil { + t.Fatal(err) + } + child4, err := db.Set("/logs", "4") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/db/logs", child4.ID()); err != nil { + t.Fatal(err) + } + + child3, err := db.Set("/sentry", "3") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/db", child2.ID()); err != nil { + t.Fatal(err) + } + + entries := db.List("/webapp", 1) + if len(entries) != 3 { + t.Fatalf("Expect 3 entries for / got %d", len(entries)) + } + + entries = db.List("/webapp", 0) + if len(entries) != 2 { + t.Fatalf("Expect 2 entries for / got %d", len(entries)) + } +} + +func TestAddSelfAsChild(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + child, err := db.Set("/test", "1") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/test/other", child.ID()); err == nil { + t.Fatal("Error should not be nil") + } +} + +func TestAddChildToNonExistantRoot(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + if _, err := db.Set("/myapp", "1"); err != nil { + t.Fatal(err) + } + + if _, err := db.Set("/myapp/proxy/db", "2"); err == nil { + t.Fatal("Error should not be nil") + } +} + +func TestWalkAll(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + _, err := db.Set("/webapp", "1") + if err != nil { + t.Fatal(err) + } + child2, err := db.Set("/db", "2") + if err != nil { + t.Fatal(err) + } + child4, err := db.Set("/db/logs", "4") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/logs", child4.ID()); err != nil { + t.Fatal(err) + } + + child3, err := db.Set("/sentry", "3") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/db", child2.ID()); err != nil { + t.Fatal(err) + } + + child5, err := db.Set("/gograph", "5") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil { + t.Fatal(err) + } + + if err := db.Walk("/", func(p string, e *Entity) error { + t.Logf("Path: %s Entity: %s", p, e.ID()) + return nil + }, -1); err != nil { + t.Fatal(err) + } +} + +func TestGetEntityByPath(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + _, err := db.Set("/webapp", "1") + if err != nil { + t.Fatal(err) + } + child2, err := db.Set("/db", "2") + if err != nil { + t.Fatal(err) + } + child4, err := db.Set("/logs", "4") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/db/logs", child4.ID()); err != nil { + t.Fatal(err) + } + + child3, err := db.Set("/sentry", "3") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/db", child2.ID()); err != nil { + t.Fatal(err) + } + + child5, err := db.Set("/gograph", "5") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil { + t.Fatal(err) + } + + entity := db.Get("/webapp/db/logs") + if entity == nil { + t.Fatal("Entity should not be nil") + } + if entity.ID() != "4" { + t.Fatalf("Expected to get entity with id 4, got %s", entity.ID()) + } +} + +func TestEnitiesPaths(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + _, err := db.Set("/webapp", "1") + if err != nil { + t.Fatal(err) + } + child2, err := db.Set("/db", "2") + if err != nil { + t.Fatal(err) + } + child4, err := db.Set("/logs", "4") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/db/logs", child4.ID()); err != nil { + t.Fatal(err) + } + + child3, err := db.Set("/sentry", "3") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/db", child2.ID()); err != nil { + t.Fatal(err) + } + + child5, err := db.Set("/gograph", "5") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil { + t.Fatal(err) + } + + out := db.List("/", -1) + for _, p := range out.Paths() { + t.Log(p) + } +} + +func TestDeleteRootEntity(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + if err := db.Delete("/"); err == nil { + t.Fatal("Error should not be nil") + } +} + +func TestDeleteEntity(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + _, err := db.Set("/webapp", "1") + if err != nil { + t.Fatal(err) + } + child2, err := db.Set("/db", "2") + if err != nil { + t.Fatal(err) + } + child4, err := db.Set("/logs", "4") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/db/logs", child4.ID()); err != nil { + t.Fatal(err) + } + + child3, err := db.Set("/sentry", "3") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/db", child2.ID()); err != nil { + t.Fatal(err) + } + + child5, err := db.Set("/gograph", "5") + if err != nil { + t.Fatal(err) + } + if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil { + t.Fatal(err) + } + + if err := db.Delete("/webapp/sentry"); err != nil { + t.Fatal(err) + } + entity := db.Get("/webapp/sentry") + if entity != nil { + t.Fatal("Entity /webapp/sentry should be nil") + } +} + +func TestCountRefs(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + db.Set("/webapp", "1") + + if db.Refs("1") != 1 { + t.Fatal("Expect reference count to be 1") + } + + db.Set("/db", "2") + db.Set("/webapp/db", "2") + if db.Refs("2") != 2 { + t.Fatal("Expect reference count to be 2") + } +} + +func TestPurgeId(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + db.Set("/webapp", "1") + + if db.Refs("1") != 1 { + t.Fatal("Expect reference count to be 1") + } + + db.Set("/db", "2") + db.Set("/webapp/db", "2") + + count, err := db.Purge("2") + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Fatal("Expected 2 references to be removed") + } +} + +func TestRename(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + db.Set("/webapp", "1") + + if db.Refs("1") != 1 { + t.Fatal("Expect reference count to be 1") + } + + db.Set("/db", "2") + db.Set("/webapp/db", "2") + + if db.Get("/webapp/db") == nil { + t.Fatal("Cannot find entity at path /webapp/db") + } + + if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil { + t.Fatal(err) + } + if db.Get("/webapp/db") != nil { + t.Fatal("Entity should not exist at /webapp/db") + } + if db.Get("/webapp/newdb") == nil { + t.Fatal("Cannot find entity at path /webapp/newdb") + } + +} + +func TestCreateMultipleNames(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + db.Set("/db", "1") + if _, err := db.Set("/myapp", "1"); err != nil { + t.Fatal(err) + } + + db.Walk("/", func(p string, e *Entity) error { + t.Logf("%s\n", p) + return nil + }, -1) +} + +func TestRefPaths(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + db.Set("/webapp", "1") + + db.Set("/db", "2") + db.Set("/webapp/db", "2") + + refs := db.RefPaths("2") + if len(refs) != 2 { + t.Fatalf("Expected reference count to be 2, got %d", len(refs)) + } + +} diff --git a/gograph/sort.go b/gograph/sort.go new file mode 100644 index 0000000000..cc936cb840 --- /dev/null +++ b/gograph/sort.go @@ -0,0 +1,27 @@ +package gograph + +import "sort" + +type pathSorter struct { + paths []string + by func(i, j string) bool +} + +func sortByDepth(paths []string) { + s := &pathSorter{paths, func(i, j string) bool { + return pathDepth(i) > pathDepth(j) + }} + sort.Sort(s) +} + +func (s *pathSorter) Len() int { + return len(s.paths) +} + +func (s *pathSorter) Swap(i, j int) { + s.paths[i], s.paths[j] = s.paths[j], s.paths[i] +} + +func (s *pathSorter) Less(i, j int) bool { + return s.by(s.paths[i], s.paths[j]) +} diff --git a/gograph/sort_test.go b/gograph/sort_test.go new file mode 100644 index 0000000000..40431039a5 --- /dev/null +++ b/gograph/sort_test.go @@ -0,0 +1,29 @@ +package gograph + +import ( + "testing" +) + +func TestSort(t *testing.T) { + paths := []string{ + "/", + "/myreallylongname", + "/app/db", + } + + sortByDepth(paths) + + if len(paths) != 3 { + t.Fatalf("Expected 3 parts got %d", len(paths)) + } + + if paths[0] != "/app/db" { + t.Fatalf("Expected /app/db got %s", paths[0]) + } + if paths[1] != "/myreallylongname" { + t.Fatalf("Expected /myreallylongname got %s", paths[1]) + } + if paths[2] != "/" { + t.Fatalf("Expected / got %s", paths[2]) + } +} diff --git a/gograph/utils.go b/gograph/utils.go new file mode 100644 index 0000000000..c20dd124a2 --- /dev/null +++ b/gograph/utils.go @@ -0,0 +1,32 @@ +package gograph + +import ( + "path" + "strings" +) + +// Split p on / +func split(p string) []string { + return strings.Split(p, "/") +} + +// Returns the depth or number of / in a given path +func pathDepth(p string) int { + parts := split(p) + if len(parts) == 2 && parts[1] == "" { + return 1 + } + return len(parts) +} + +func splitPath(p string) (parent, name string) { + if p[0] != '/' { + p = "/" + p + } + parent, name = path.Split(p) + l := len(parent) + if parent[l-1] == '/' { + parent = parent[:l-1] + } + return +} diff --git a/hack/make.sh b/hack/make.sh index 4eecfd0484..9d960a0efc 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -45,7 +45,8 @@ if [ -n "$(git status --porcelain)" ]; then fi # Use these flags when compiling the tests and final binary -LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w" +LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' +BUILDFLAGS='-tags netgo' bundle() { diff --git a/hack/make/binary b/hack/make/binary index cff9f5c733..4c2c248975 100644 --- a/hack/make/binary +++ b/hack/make/binary @@ -2,6 +2,6 @@ DEST=$1 -if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then - echo "Created binary: $DEST/docker-$VERSION" -fi +go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker + +echo "Created binary: $DEST/docker-$VERSION" diff --git a/hack/make/test b/hack/make/test index 9554f2946b..c8ed1bc7c0 100644 --- a/hack/make/test +++ b/hack/make/test @@ -15,7 +15,7 @@ bundle_test() { set -x cd $test_dir go test -i - go test -v -ldflags "$LDFLAGS" $TESTFLAGS + go test -v -ldflags "$LDFLAGS" $BUILDFLAGS $TESTFLAGS ) done } 2>&1 | tee $DEST/test.log } diff --git a/iptables/MAINTAINERS b/iptables/MAINTAINERS new file mode 100644 index 0000000000..1e998f8ac1 --- /dev/null +++ b/iptables/MAINTAINERS @@ -0,0 +1 @@ +Michael Crosby (@crosbymichael) diff --git a/iptables/iptables.go b/iptables/iptables.go new file mode 100644 index 0000000000..5974d4d9c6 --- /dev/null +++ b/iptables/iptables.go @@ -0,0 +1,105 @@ +package iptables + +import ( + "errors" + "fmt" + "net" + "os/exec" + "strconv" + "strings" +) + +type Action string + +const ( + Add Action = "-A" + Delete Action = "-D" +) + +var ( + ErrIptablesNotFound = errors.New("Iptables not found") + nat = []string{"-t", "nat"} +) + +type Chain struct { + Name string + Bridge string +} + +func NewChain(name, bridge string) (*Chain, error) { + if err := Raw("-t", "nat", "-N", name); err != nil { + return nil, err + } + chain := &Chain{ + Name: name, + Bridge: bridge, + } + + if err := chain.Prerouting(Add, "-m", "addrtype", "--dst-type", "LOCAL"); err != nil { + return nil, fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err) + } + if err := chain.Output(Add, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8"); err != nil { + return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) + } + return chain, nil +} + +func RemoveExistingChain(name string) error { + chain := &Chain{ + Name: name, + } + return chain.Remove() +} + +func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error { + return Raw("-t", "nat", fmt.Sprint(action), c.Name, + "-p", proto, + "-d", ip.String(), + "--dport", strconv.Itoa(port), + "!", "-i", c.Bridge, + "-j", "DNAT", + "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) +} + +func (c *Chain) Prerouting(action Action, args ...string) error { + a := append(nat, fmt.Sprint(action), "PREROUTING") + if len(args) > 0 { + a = append(a, args...) + } + return Raw(append(a, "-j", c.Name)...) +} + +func (c *Chain) Output(action Action, args ...string) error { + a := append(nat, fmt.Sprint(action), "OUTPUT") + if len(args) > 0 { + a = append(a, args...) + } + return Raw(append(a, "-j", c.Name)...) +} + +func (c *Chain) Remove() error { + // Ignore errors - This could mean the chains were never set up + c.Prerouting(Delete, "-m", "addrtype", "--dst-type", "LOCAL") + c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8") + c.Output(Delete, "-m", "addrtype", "--dst-type", "LOCAL") // Created in versions <= 0.1.6 + + c.Prerouting(Delete) + c.Output(Delete) + + Raw("-t", "nat", "-F", c.Name) + Raw("-t", "nat", "-X", c.Name) + + return nil +} + +func Raw(args ...string) error { + path, err := exec.LookPath("iptables") + if err != nil { + return ErrIptablesNotFound + } + if err := exec.Command(path, args...).Run(); err != nil { + return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " ")) + } + return nil + +} diff --git a/iptables/iptables_test.go b/iptables/iptables_test.go new file mode 100644 index 0000000000..aad8acdb81 --- /dev/null +++ b/iptables/iptables_test.go @@ -0,0 +1,18 @@ +package iptables + +import ( + "os" + "testing" +) + +func TestIptables(t *testing.T) { + if err := Raw("-L"); err != nil { + t.Fatal(err) + } + path := os.Getenv("PATH") + os.Setenv("PATH", "") + defer os.Setenv("PATH", path) + if err := Raw("-L"); err == nil { + t.Fatal("Not finding iptables in the PATH should cause an error") + } +} diff --git a/links.go b/links.go new file mode 100644 index 0000000000..f1087ec34a --- /dev/null +++ b/links.go @@ -0,0 +1,141 @@ +package docker + +import ( + "fmt" + "github.com/dotcloud/docker/iptables" + "path" + "strings" +) + +type Link struct { + ParentIP string + ChildIP string + Name string + BridgeInterface string + ChildEnvironment []string + Ports []Port + IsEnabled bool +} + +func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, error) { + if parent.ID == child.ID { + return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID) + } + if !child.State.Running { + return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.ID, name) + } + + ports := make([]Port, len(child.Config.ExposedPorts)) + var i int + for p := range child.Config.ExposedPorts { + ports[i] = p + i++ + } + + l := &Link{ + BridgeInterface: bridgeInterface, + Name: name, + ChildIP: child.NetworkSettings.IPAddress, + ParentIP: parent.NetworkSettings.IPAddress, + ChildEnvironment: child.Config.Env, + Ports: ports, + } + return l, nil + +} + +func (l *Link) Alias() string { + _, alias := path.Split(l.Name) + return alias +} + +func (l *Link) ToEnv() []string { + env := []string{} + alias := strings.ToUpper(l.Alias()) + + if p := l.getDefaultPort(); p != nil { + env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port())) + } + + // Load exposed ports into the environment + for _, p := range l.Ports { + env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.ChildIP, p.Port())) + } + + // Load the linked container's name into the environment + env = append(env, fmt.Sprintf("%s_NAME=%s", alias, l.Name)) + + if l.ChildEnvironment != nil { + for _, v := range l.ChildEnvironment { + parts := strings.Split(v, "=") + if len(parts) != 2 { + continue + } + // Ignore a few variables that are added during docker build + if parts[0] == "HOME" || parts[0] == "PATH" { + continue + } + env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1])) + } + } + return env +} + +// Default port rules +func (l *Link) getDefaultPort() *Port { + var p Port + i := len(l.Ports) + + if i == 0 { + return nil + } else if i > 1 { + sortPorts(l.Ports, func(ip, jp Port) bool { + // If the two ports have the same number, tcp takes priority + // Sort in desc order + return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp") + }) + } + p = l.Ports[0] + return &p +} + +func (l *Link) Enable() error { + if err := l.toggle("-I", false); err != nil { + return err + } + l.IsEnabled = true + return nil +} + +func (l *Link) Disable() { + // We do not care about errors here because the link may not + // exist in iptables + l.toggle("-D", true) + + l.IsEnabled = false +} + +func (l *Link) toggle(action string, ignoreErrors bool) error { + for _, p := range l.Ports { + if err := iptables.Raw(action, "FORWARD", + "-i", l.BridgeInterface, "-o", l.BridgeInterface, + "-p", p.Proto(), + "-s", l.ParentIP, + "--dport", p.Port(), + "-d", l.ChildIP, + "-j", "ACCEPT"); !ignoreErrors && err != nil { + return err + } + + if err := iptables.Raw(action, "FORWARD", + "-i", l.BridgeInterface, "-o", l.BridgeInterface, + "-p", p.Proto(), + "-s", l.ChildIP, + "--sport", p.Port(), + "-d", l.ParentIP, + "-j", "ACCEPT"); !ignoreErrors && err != nil { + return err + } + } + return nil +} diff --git a/links_test.go b/links_test.go new file mode 100644 index 0000000000..64608e15c1 --- /dev/null +++ b/links_test.go @@ -0,0 +1,104 @@ +package docker + +import ( + "strings" + "testing" +) + +func newMockLinkContainer(id string, ip string) *Container { + return &Container{ + Config: &Config{}, + ID: id, + NetworkSettings: &NetworkSettings{ + IPAddress: ip, + }, + } +} + +func TestLinkNew(t *testing.T) { + toID := GenerateID() + fromID := GenerateID() + + from := newMockLinkContainer(fromID, "172.0.17.2") + from.Config.Env = []string{} + from.State = State{Running: true} + ports := make(map[Port]struct{}) + + ports[Port("6379/tcp")] = struct{}{} + + from.Config.ExposedPorts = ports + + to := newMockLinkContainer(toID, "172.0.17.3") + + link, err := NewLink(to, from, "/db/docker", "172.0.17.1") + if err != nil { + t.Fatal(err) + } + + if link == nil { + t.FailNow() + } + if link.Name != "/db/docker" { + t.Fail() + } + if link.Alias() != "docker" { + t.Fail() + } + if link.ParentIP != "172.0.17.3" { + t.Fail() + } + if link.ChildIP != "172.0.17.2" { + t.Fail() + } + if link.BridgeInterface != "172.0.17.1" { + t.Fail() + } + for _, p := range link.Ports { + if p != Port("6379/tcp") { + t.Fail() + } + } +} + +func TestLinkEnv(t *testing.T) { + toID := GenerateID() + fromID := GenerateID() + + from := newMockLinkContainer(fromID, "172.0.17.2") + from.Config.Env = []string{"PASSWORD=gordon"} + from.State = State{Running: true} + ports := make(map[Port]struct{}) + + ports[Port("6379/tcp")] = struct{}{} + + from.Config.ExposedPorts = ports + + to := newMockLinkContainer(toID, "172.0.17.3") + + link, err := NewLink(to, from, "/db/docker", "172.0.17.1") + if err != nil { + t.Fatal(err) + } + + rawEnv := link.ToEnv() + env := make(map[string]string, len(rawEnv)) + for _, e := range rawEnv { + parts := strings.Split(e, "=") + if len(parts) != 2 { + t.FailNow() + } + env[parts[0]] = parts[1] + } + if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" { + t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT"]) + } + if env["DOCKER_PORT_6379_TCP"] != "tcp://172.0.17.2:6379" { + t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT_6379_TCP"]) + } + if env["DOCKER_NAME"] != "/db/docker" { + t.Fatalf("Expected /db/docker, got %s", env["DOCKER_NAME"]) + } + if env["DOCKER_ENV_PASSWORD"] != "gordon" { + t.Fatalf("Expected gordon, got %s", env["DOCKER_ENV_PASSWORD"]) + } +} diff --git a/network.go b/network.go index 59d82577d8..b480dc1440 100644 --- a/network.go +++ b/network.go @@ -4,6 +4,8 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/dotcloud/docker/iptables" + "github.com/dotcloud/docker/proxy" "github.com/dotcloud/docker/utils" "log" "net" @@ -13,8 +15,6 @@ import ( "sync" ) -var NetworkBridgeIface string - const ( DefaultNetworkBridge = "docker0" DisableNetworkBridge = "none" @@ -81,18 +81,6 @@ func ip(args ...string) (string, error) { return string(output), nil } -// Wrapper around the iptables command -func iptables(args ...string) error { - path, err := exec.LookPath("iptables") - if err != nil { - return fmt.Errorf("command not found: iptables") - } - if err := exec.Command(path, args...).Run(); err != nil { - return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " ")) - } - return nil -} - func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error { utils.Debugf("Routes:\n\n%s", routes) for _, line := range strings.Split(routes, "\n") { @@ -124,7 +112,7 @@ func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error { // CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, // and attempts to configure it with an address which doesn't conflict with any other interface on the host. // If it can't find an address which doesn't conflict, it will return an error. -func CreateBridgeIface(ifaceName string) error { +func CreateBridgeIface(config *DaemonConfig) error { addrs := []string{ // Here we don't follow the convention of using the 1st IP of the range for the gateway. // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. @@ -163,23 +151,29 @@ func CreateBridgeIface(ifaceName string) error { } } 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) + return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", config.BridgeIface, config.BridgeIface) } - utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr) + utils.Debugf("Creating bridge %s with network %s", config.BridgeIface, ifaceAddr) - if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil { + if output, err := ip("link", "add", config.BridgeIface, "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 { + if output, err := ip("addr", "add", ifaceAddr, "dev", config.BridgeIface); err != nil { return fmt.Errorf("Unable to add private network: %s (%s)", err, output) } - if output, err := ip("link", "set", ifaceName, "up"); err != nil { + if output, err := ip("link", "set", config.BridgeIface, "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) + if config.EnableIptables { + if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, + "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { + return fmt.Errorf("Unable to enable network bridge NAT: %s", err) + } + // Prevent inter-container communication by default + if err := iptables.Raw("-A", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"); err != nil { + return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) + } } return nil } @@ -216,58 +210,27 @@ func getIfaceAddr(name string) (net.Addr, error) { // It keeps track of all mappings and is able to unmap at will type PortMapper struct { tcpMapping map[int]*net.TCPAddr - tcpProxies map[int]Proxy + tcpProxies map[int]proxy.Proxy udpMapping map[int]*net.UDPAddr - udpProxies map[int]Proxy + udpProxies map[int]proxy.Proxy + + iptables *iptables.Chain + defaultIp net.IP } -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", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER") - iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6 - // 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") - iptables("-t", "nat", "-X", "DOCKER") - mapper.tcpMapping = make(map[int]*net.TCPAddr) - mapper.tcpProxies = make(map[int]Proxy) - mapper.udpMapping = make(map[int]*net.UDPAddr) - mapper.udpProxies = make(map[int]Proxy) - return nil -} - -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", "-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", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil { - return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) - } - return nil -} - -func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error { - return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port), - "!", "-i", NetworkBridgeIface, - "-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) -} - -func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { +func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) error { if _, isTCP := backendAddr.(*net.TCPAddr); isTCP { backendPort := backendAddr.(*net.TCPAddr).Port backendIP := backendAddr.(*net.TCPAddr).IP - if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil { - return err + if mapper.iptables != nil { + if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil { + return err + } } mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr) - proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr) + proxy, err := proxy.NewProxy(&net.TCPAddr{IP: ip, Port: port}, backendAddr) if err != nil { - mapper.Unmap(port, "tcp") + mapper.Unmap(ip, port, "tcp") return err } mapper.tcpProxies[port] = proxy @@ -275,13 +238,15 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { } else { backendPort := backendAddr.(*net.UDPAddr).Port backendIP := backendAddr.(*net.UDPAddr).IP - if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil { - return err + if mapper.iptables != nil { + if err := mapper.iptables.Forward(iptables.Add, ip, port, "udp", backendIP.String(), backendPort); err != nil { + return err + } } mapper.udpMapping[port] = backendAddr.(*net.UDPAddr) - proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: port}, backendAddr) + proxy, err := proxy.NewProxy(&net.UDPAddr{IP: ip, Port: port}, backendAddr) if err != nil { - mapper.Unmap(port, "udp") + mapper.Unmap(ip, port, "udp") return err } mapper.udpProxies[port] = proxy @@ -290,7 +255,7 @@ func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { return nil } -func (mapper *PortMapper) Unmap(port int, proto string) error { +func (mapper *PortMapper) Unmap(ip net.IP, port int, proto string) error { if proto == "tcp" { backendAddr, ok := mapper.tcpMapping[port] if !ok { @@ -300,8 +265,10 @@ func (mapper *PortMapper) Unmap(port int, proto string) error { proxy.Close() delete(mapper.tcpProxies, port) } - if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { - return err + if mapper.iptables != nil { + if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { + return err + } } delete(mapper.tcpMapping, port) } else { @@ -313,21 +280,37 @@ func (mapper *PortMapper) Unmap(port int, proto string) error { proxy.Close() delete(mapper.udpProxies, port) } - if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { - return err + if mapper.iptables != nil { + if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { + return err + } } delete(mapper.udpMapping, port) } return nil } -func newPortMapper() (*PortMapper, error) { - mapper := &PortMapper{} - if err := mapper.cleanup(); err != nil { +func newPortMapper(config *DaemonConfig) (*PortMapper, error) { + // We can always try removing the iptables + if err := iptables.RemoveExistingChain("DOCKER"); err != nil { return nil, err } - if err := mapper.setup(); err != nil { - return nil, err + var chain *iptables.Chain + if config.EnableIptables { + var err error + chain, err = iptables.NewChain("DOCKER", config.BridgeIface) + if err != nil { + return nil, fmt.Errorf("Failed to create DOCKER chain: %s", err) + } + } + + mapper := &PortMapper{ + tcpMapping: make(map[int]*net.TCPAddr), + tcpProxies: make(map[int]proxy.Proxy), + udpMapping: make(map[int]*net.UDPAddr), + udpProxies: make(map[int]proxy.Proxy), + iptables: chain, + defaultIp: config.DefaultIp, } return mapper, nil } @@ -519,40 +502,56 @@ type NetworkInterface struct { disabled bool } -// Allocate an external TCP port and map it to the interface -func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { +// Allocate an external port and map it to the interface +func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*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) + ip := iface.manager.portMapper.defaultIp + + if binding.HostIp != "" { + ip = net.ParseIP(binding.HostIp) + } else { + binding.HostIp = ip.String() + } + + nat := &Nat{ + Port: port, + Binding: binding, + } + + containerPort, err := parsePort(port.Port()) if err != nil { return nil, err } - if nat.Proto == "tcp" { - extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend) + hostPort, _ := parsePort(nat.Binding.HostPort) + + if nat.Port.Proto() == "tcp" { + extPort, err := iface.manager.tcpPortAllocator.Acquire(hostPort) if err != nil { return nil, err } - backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend} - if err := iface.manager.portMapper.Map(extPort, backend); err != nil { + + backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort} + if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { iface.manager.tcpPortAllocator.Release(extPort) return nil, err } - nat.Frontend = extPort + nat.Binding.HostPort = strconv.Itoa(extPort) } else { - extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend) + extPort, err := iface.manager.udpPortAllocator.Acquire(hostPort) if err != nil { return nil, err } - backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend} - if err := iface.manager.portMapper.Map(extPort, backend); err != nil { + backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort} + if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { iface.manager.udpPortAllocator.Release(extPort) return nil, err } - nat.Frontend = extPort + nat.Binding.HostPort = strconv.Itoa(extPort) } iface.extPorts = append(iface.extPorts, nat) @@ -560,83 +559,37 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { } type Nat struct { - Proto string - Frontend int - Backend int + Port Port + Binding PortBinding } -func parseNat(spec string) (*Nat, error) { - var nat Nat - - if strings.Contains(spec, "/") { - specParts := strings.Split(spec, "/") - if len(specParts) != 2 { - return nil, fmt.Errorf("Invalid port format.") - } - proto := specParts[1] - spec = specParts[0] - if proto != "tcp" && proto != "udp" { - return nil, fmt.Errorf("Invalid port format: unknown protocol %v.", proto) - } - nat.Proto = proto - } else { - nat.Proto = "tcp" - } - - if strings.Contains(spec, ":") { - specParts := strings.Split(spec, ":") - if len(specParts) != 2 { - return nil, fmt.Errorf("Invalid port format.") - } - // 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 len(specParts[0]) == 0 { - sameFrontend = true - } else { - front, err := strconv.ParseUint(specParts[0], 10, 16) - if err != nil { - return nil, err - } - nat.Frontend = int(front) - } - back, err := strconv.ParseUint(specParts[1], 10, 16) - if err != nil { - return nil, err - } - nat.Backend = int(back) - if sameFrontend { - nat.Frontend = nat.Backend - } - } else { - port, err := strconv.ParseUint(spec, 10, 16) - if err != nil { - return nil, err - } - nat.Backend = int(port) - } - - return &nat, nil +func (n *Nat) String() string { + return fmt.Sprintf("%s:%d:%d/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto()) } // Release: Network cleanup - release all resources func (iface *NetworkInterface) Release() { - if iface.disabled { return } for _, nat := range iface.extPorts { - utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend) - if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil { - log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err) + hostPort, err := parsePort(nat.Binding.HostPort) + if err != nil { + log.Printf("Unable to get host port: %s", err) + continue } - if nat.Proto == "tcp" { - if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil { - log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err) + ip := net.ParseIP(nat.Binding.HostIp) + utils.Debugf("Unmaping %s/%s", nat.Port.Proto, nat.Binding.HostPort) + if err := iface.manager.portMapper.Unmap(ip, hostPort, nat.Port.Proto()); err != nil { + log.Printf("Unable to unmap port %s: %s", nat, err) + } + if nat.Port.Proto() == "tcp" { + if err := iface.manager.tcpPortAllocator.Release(hostPort); err != nil { + log.Printf("Unable to release port %s", nat) } - } else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil { - log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err) + } else if err := iface.manager.udpPortAllocator.Release(hostPort); err != nil { + log.Printf("Unable to release port %s: %s", nat, err) } } @@ -704,22 +657,21 @@ func (manager *NetworkManager) Close() error { return err3 } -func newNetworkManager(bridgeIface string) (*NetworkManager, error) { - - if bridgeIface == DisableNetworkBridge { +func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { + if config.BridgeIface == DisableNetworkBridge { manager := &NetworkManager{ disabled: true, } return manager, nil } - addr, err := getIfaceAddr(bridgeIface) + addr, err := getIfaceAddr(config.BridgeIface) if err != nil { // If the iface is not found, try to create it - if err := CreateBridgeIface(bridgeIface); err != nil { + if err := CreateBridgeIface(config); err != nil { return nil, err } - addr, err = getIfaceAddr(bridgeIface) + addr, err = getIfaceAddr(config.BridgeIface) if err != nil { return nil, err } @@ -737,13 +689,13 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) { return nil, err } - portMapper, err := newPortMapper() + portMapper, err := newPortMapper(config) if err != nil { return nil, err } manager := &NetworkManager{ - bridgeIface: bridgeIface, + bridgeIface: config.BridgeIface, bridgeNetwork: network, ipAllocator: ipAllocator, tcpPortAllocator: tcpPortAllocator, diff --git a/network_test.go b/network_test.go index bd3a16a1be..a0492b4907 100644 --- a/network_test.go +++ b/network_test.go @@ -2,117 +2,9 @@ package docker import ( "net" - "os" "testing" ) -func TestIptables(t *testing.T) { - if err := iptables("-L"); err != nil { - t.Fatal(err) - } - path := os.Getenv("PATH") - os.Setenv("PATH", "") - defer os.Setenv("PATH", path) - if err := iptables("-L"); err == nil { - t.Fatal("Not finding iptables in the PATH should cause an error") - } -} - -func TestParseNat(t *testing.T) { - if nat, err := parseNat("4500"); err == nil { - if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" { - t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat(":4501"); err == nil { - if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" { - t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4502:4503"); err == nil { - if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { - t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4502:4503/tcp"); err == nil { - if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { - t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4502:4503/udp"); err == nil { - if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" { - t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat(":4503/udp"); err == nil { - if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" { - t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat(":4503/tcp"); err == nil { - if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" { - t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4503/tcp"); err == nil { - if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" { - t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if nat, err := parseNat("4503/udp"); err == nil { - if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" { - t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s", - nat.Frontend, nat.Backend, nat.Proto) - } - } else { - t.Fatal(err) - } - - if _, err := parseNat("4503/tcpgarbage"); err == nil { - t.Fatal(err) - } - - if _, err := parseNat("4503/tcp/udp"); err == nil { - t.Fatal(err) - } - - if _, err := parseNat("4503/"); err == nil { - t.Fatal(err) - } -} - func TestPortAllocation(t *testing.T) { allocator, err := newPortAllocator() if err != nil { diff --git a/proxy/MAINTAINERS b/proxy/MAINTAINERS new file mode 100644 index 0000000000..1e998f8ac1 --- /dev/null +++ b/proxy/MAINTAINERS @@ -0,0 +1 @@ +Michael Crosby (@crosbymichael) diff --git a/network_proxy_test.go b/proxy/network_proxy_test.go similarity index 99% rename from network_proxy_test.go rename to proxy/network_proxy_test.go index be506795b0..9e382567c5 100644 --- a/network_proxy_test.go +++ b/proxy/network_proxy_test.go @@ -1,4 +1,4 @@ -package docker +package proxy import ( "bytes" diff --git a/proxy/proxy.go b/proxy/proxy.go new file mode 100644 index 0000000000..7a711f657b --- /dev/null +++ b/proxy/proxy.go @@ -0,0 +1,29 @@ +package proxy + +import ( + "fmt" + "net" +) + +type Proxy interface { + // Start forwarding traffic back and forth the front and back-end + // addresses. + Run() + // Stop forwarding traffic and close both ends of the Proxy. + Close() + // Return the address on which the proxy is listening. + FrontendAddr() net.Addr + // Return the proxied address. + BackendAddr() net.Addr +} + +func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { + switch frontendAddr.(type) { + case *net.UDPAddr: + return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr)) + case *net.TCPAddr: + return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr)) + default: + panic(fmt.Errorf("Unsupported protocol")) + } +} diff --git a/proxy/tcp_proxy.go b/proxy/tcp_proxy.go new file mode 100644 index 0000000000..e7c460f61d --- /dev/null +++ b/proxy/tcp_proxy.go @@ -0,0 +1,93 @@ +package proxy + +import ( + "github.com/dotcloud/docker/utils" + "io" + "log" + "net" + "syscall" +) + +type TCPProxy struct { + listener *net.TCPListener + frontendAddr *net.TCPAddr + backendAddr *net.TCPAddr +} + +func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) { + listener, err := net.ListenTCP("tcp", frontendAddr) + if err != nil { + return nil, err + } + // If the port in frontendAddr was 0 then ListenTCP will have a picked + // a port to listen on, hence the call to Addr to get that actual port: + return &TCPProxy{ + listener: listener, + frontendAddr: listener.Addr().(*net.TCPAddr), + backendAddr: backendAddr, + }, nil +} + +func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { + backend, err := net.DialTCP("tcp", nil, proxy.backendAddr) + if err != nil { + log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error()) + client.Close() + return + } + + event := make(chan int64) + var broker = func(to, from *net.TCPConn) { + written, err := io.Copy(to, from) + if err != nil { + // If the socket we are writing to is shutdown with + // SHUT_WR, forward it to the other end of the pipe: + if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE { + from.CloseWrite() + } + } + to.CloseRead() + event <- written + } + utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr()) + go broker(client, backend) + go broker(backend, client) + + var transferred int64 = 0 + for i := 0; i < 2; i++ { + select { + case written := <-event: + transferred += written + case <-quit: + // Interrupt the two brokers and "join" them. + client.Close() + backend.Close() + for ; i < 2; i++ { + transferred += <-event + } + goto done + } + } + client.Close() + backend.Close() +done: + utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr()) +} + +func (proxy *TCPProxy) Run() { + quit := make(chan bool) + defer close(quit) + utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr) + for { + client, err := proxy.listener.Accept() + if err != nil { + utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error()) + return + } + go proxy.clientLoop(client.(*net.TCPConn), quit) + } +} + +func (proxy *TCPProxy) Close() { proxy.listener.Close() } +func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } +func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } diff --git a/network_proxy.go b/proxy/udp_proxy.go similarity index 55% rename from network_proxy.go rename to proxy/udp_proxy.go index 86545801b5..7d34988f70 100644 --- a/network_proxy.go +++ b/proxy/udp_proxy.go @@ -1,10 +1,8 @@ -package docker +package proxy import ( "encoding/binary" - "fmt" "github.com/dotcloud/docker/utils" - "io" "log" "net" "sync" @@ -17,107 +15,6 @@ const ( UDPBufSize = 2048 ) -type Proxy interface { - // Start forwarding traffic back and forth the front and back-end - // addresses. - Run() - // Stop forwarding traffic and close both ends of the Proxy. - Close() - // Return the address on which the proxy is listening. - FrontendAddr() net.Addr - // Return the proxied address. - BackendAddr() net.Addr -} - -type TCPProxy struct { - listener *net.TCPListener - frontendAddr *net.TCPAddr - backendAddr *net.TCPAddr -} - -func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) { - listener, err := net.ListenTCP("tcp", frontendAddr) - if err != nil { - return nil, err - } - // If the port in frontendAddr was 0 then ListenTCP will have a picked - // a port to listen on, hence the call to Addr to get that actual port: - return &TCPProxy{ - listener: listener, - frontendAddr: listener.Addr().(*net.TCPAddr), - backendAddr: backendAddr, - }, nil -} - -func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { - backend, err := net.DialTCP("tcp", nil, proxy.backendAddr) - if err != nil { - log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error()) - client.Close() - return - } - - event := make(chan int64) - var broker = func(to, from *net.TCPConn) { - written, err := io.Copy(to, from) - if err != nil { - // If the socket we are writing to is shutdown with - // SHUT_WR, forward it to the other end of the pipe: - if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE { - from.CloseWrite() - } - } - to.CloseRead() - event <- written - } - utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr()) - go broker(client, backend) - go broker(backend, client) - - var transferred int64 = 0 - for i := 0; i < 2; i++ { - select { - case written := <-event: - transferred += written - case <-quit: - // Interrupt the two brokers and "join" them. - client.Close() - backend.Close() - for ; i < 2; i++ { - transferred += <-event - } - goto done - } - } - client.Close() - backend.Close() -done: - utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr()) -} - -func (proxy *TCPProxy) Run() { - quit := make(chan bool) - defer close(quit) - - utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr) - for { - client, err := proxy.listener.Accept() - if err != nil { - if utils.IsClosedError(err) { - utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (socket was closed)", proxy.frontendAddr, proxy.backendAddr) - } else { - utils.Errorf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error()) - } - return - } - go proxy.clientLoop(client.(*net.TCPConn), quit) - } -} - -func (proxy *TCPProxy) Close() { proxy.listener.Close() } -func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } -func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } - // A net.Addr where the IP is split into two fields so you can use it as a key // in a map: type connTrackKey struct { @@ -253,14 +150,3 @@ func (proxy *UDPProxy) Close() { func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } - -func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { - switch frontendAddr.(type) { - case *net.UDPAddr: - return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr)) - case *net.TCPAddr: - return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr)) - default: - panic(fmt.Errorf("Unsupported protocol")) - } -} diff --git a/runtime.go b/runtime.go index 81516e1e94..2b063351fc 100644 --- a/runtime.go +++ b/runtime.go @@ -3,6 +3,7 @@ package docker import ( "container/list" "fmt" + "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -24,7 +25,6 @@ type Capabilities struct { } type Runtime struct { - root string repository string containers *list.List networkManager *NetworkManager @@ -32,10 +32,10 @@ type Runtime struct { repositories *TagStore idIndex *utils.TruncIndex capabilities *Capabilities - autoRestart bool volumes *Graph srv *Server - Dns []string + config *DaemonConfig + containerGraph *gograph.Database } var sysInitPath string @@ -66,10 +66,15 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { // Get looks for a container by the specified ID or name, and returns it. // If the container is not found, or if an error occurs, nil is returned. func (runtime *Runtime) Get(name string) *Container { + if c, _ := runtime.GetByName(name); c != nil { + return c + } + id, err := runtime.idIndex.Get(name) if err != nil { return nil } + e := runtime.getContainerElement(id) if e == nil { return nil @@ -87,10 +92,9 @@ func (runtime *Runtime) containerRoot(id string) string { return path.Join(runtime.repository, id) } -// Load reads the contents of a container from disk and registers -// it with Register. +// Load reads the contents of a container from disk // This is typically done at startup. -func (runtime *Runtime) Load(id string) (*Container, error) { +func (runtime *Runtime) load(id string) (*Container, error) { container := &Container{root: runtime.containerRoot(id)} if err := container.FromDisk(); err != nil { return nil, err @@ -101,9 +105,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) { if container.State.Running { container.State.Ghost = true } - if err := runtime.Register(container); err != nil { - return nil, err - } return container, nil } @@ -148,11 +149,11 @@ func (runtime *Runtime) Register(container *Container) error { } if !strings.Contains(string(output), "RUNNING") { utils.Debugf("Container %s was supposed to be running be is not.", container.ID) - if runtime.autoRestart { + if runtime.config.AutoRestart { utils.Debugf("Restarting") container.State.Ghost = false container.State.setStopped(0) - hostConfig := &HostConfig{} + hostConfig, _ := container.ReadHostConfig() if err := container.Start(hostConfig); err != nil { return err } @@ -172,9 +173,9 @@ func (runtime *Runtime) Register(container *Container) error { if !container.State.Running { close(container.waitLock) } else if !nomonitor { - container.allocateNetwork() - // hostConfig isn't needed here and can be nil - go container.monitor(nil) + hostConfig, _ := container.ReadHostConfig() + container.allocateNetwork(hostConfig) + go container.monitor(hostConfig) } return nil } @@ -202,6 +203,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := container.Stop(3); err != nil { return err } + if mounted, err := container.Mounted(); err != nil { return err } else if mounted { @@ -209,6 +211,11 @@ func (runtime *Runtime) Destroy(container *Container) error { return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err) } } + + if _, err := runtime.containerGraph.Purge(container.ID); err != nil { + utils.Debugf("Unable to remove container from link graph: %s", err) + } + // Deregister the container before removing its directory, to avoid race conditions runtime.idIndex.Delete(container.ID) runtime.containers.Remove(element) @@ -227,9 +234,10 @@ func (runtime *Runtime) restore() error { if err != nil { return err } + containers := []*Container{} for i, v := range dir { id := v.Name() - container, err := runtime.Load(id) + container, err := runtime.load(id) if i%21 == 0 && os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf("\b%c", wheel[i%4]) } @@ -238,10 +246,30 @@ func (runtime *Runtime) restore() error { continue } utils.Debugf("Loaded container %v", container.ID) + containers = append(containers, container) + } + sortContainers(containers, func(i, j *Container) bool { + ic, _ := i.ReadHostConfig() + jc, _ := j.ReadHostConfig() + + if ic == nil || ic.Links == nil { + return true + } + if jc == nil || jc.Links == nil { + return false + } + return len(ic.Links) < len(jc.Links) + }) + for _, container := range containers { + if err := runtime.Register(container); err != nil { + utils.Debugf("Failed to register container %s: %s", container.ID, err) + continue + } } if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf("\bdone.\n") } + return nil } @@ -274,27 +302,45 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) { } // Create creates a new container from the given configuration. -func (runtime *Runtime) Create(config *Config) (*Container, error) { +func (runtime *Runtime) Create(config *Config) (*Container, []string, error) { // Lookup image img, err := runtime.repositories.LookupImage(config.Image) if err != nil { - return nil, err + return nil, nil, err } + warnings := []string{} if img.Config != nil { - if err := MergeConfig(config, img.Config); err != nil { - return nil, err + if img.Config.PortSpecs != nil && warnings != nil { + for _, p := range img.Config.PortSpecs { + if strings.Contains(p, ":") { + warnings = append(warnings, "This image expects private ports to be mapped to public ports on your host. "+ + "This has been deprecated and the public mappings will not be honored."+ + "Use -p to publish the ports.") + break + } + } } + if err := MergeConfig(config, img.Config); err != nil { + return nil, nil, err + } + } if len(config.Entrypoint) != 0 && config.Cmd == nil { config.Cmd = []string{} } else if config.Cmd == nil || len(config.Cmd) == 0 { - return nil, fmt.Errorf("No command specified") + return nil, nil, fmt.Errorf("No command specified") } // Generate id id := GenerateID() + + // Set the default enitity in the graph + if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", id), id); err != nil { + return nil, nil, err + } + // Generate default hostname // FIXME: the lxc template no longer needs to set a default hostname if config.Hostname == "" { @@ -328,36 +374,36 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { // Step 1: create the container directory. // This doubles as a barrier to avoid race conditions. if err := os.Mkdir(container.root, 0700); err != nil { - return nil, err + return nil, nil, err } resolvConf, err := utils.GetResolvConf() if err != nil { - return nil, err + return nil, nil, err } - if len(config.Dns) == 0 && len(runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) { + if len(config.Dns) == 0 && len(runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { //"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns - runtime.Dns = defaultDns + runtime.config.Dns = defaultDns } // If custom dns exists, then create a resolv.conf for the container - if len(config.Dns) > 0 || len(runtime.Dns) > 0 { + if len(config.Dns) > 0 || len(runtime.config.Dns) > 0 { var dns []string if len(config.Dns) > 0 { dns = config.Dns } else { - dns = runtime.Dns + dns = runtime.config.Dns } container.ResolvConfPath = path.Join(container.root, "resolv.conf") f, err := os.Create(container.ResolvConfPath) if err != nil { - return nil, err + return nil, nil, err } defer f.Close() for _, dns := range dns { if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { - return nil, err + return nil, nil, err } } } else { @@ -366,7 +412,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { // Step 2: save the container json if err := container.ToDisk(); err != nil { - return nil, err + return nil, nil, err } // Step 3: if hostname, build hostname and hosts files @@ -396,9 +442,9 @@ ff02::2 ip6-allrouters // Step 4: register the container if err := runtime.Register(container); err != nil { - return nil, err + return nil, nil, err } - return container, nil + return container, warnings, nil } // Commit creates a new filesystem image from the current state of a container. @@ -428,13 +474,85 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a return img, nil } -// FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) +func (runtime *Runtime) GetByName(name string) (*Container, error) { + if id, err := runtime.idIndex.Get(name); err == nil { + name = id + } + + entity := runtime.containerGraph.Get(name) + if entity == nil { + return nil, fmt.Errorf("Could not find entity for %s", name) + } + e := runtime.getContainerElement(entity.ID()) + if e == nil { + return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID()) + } + return e.Value.(*Container), nil +} + +func (runtime *Runtime) Children(name string) (map[string]*Container, error) { + children := make(map[string]*Container) + + err := runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error { + c := runtime.Get(e.ID()) + if c == nil { + return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p) + } + children[p] = c + return nil + }, 0) + + if err != nil { + return nil, err + } + return children, nil +} + +func (runtime *Runtime) RenameLink(oldName, newName string) error { + if id, err := runtime.idIndex.Get(oldName); err == nil { + oldName = id + } + entity := runtime.containerGraph.Get(oldName) + if entity == nil { + return fmt.Errorf("Could not find entity for %s", oldName) + } + + // This is not rename but adding a new link for the default name + // Strip the leading '/' + if entity.ID() == oldName[1:] { + _, err := runtime.containerGraph.Set(newName, entity.ID()) + return err + } + return runtime.containerGraph.Rename(oldName, newName) +} + +func (runtime *Runtime) Link(parentName, childName, alias string) error { + if id, err := runtime.idIndex.Get(parentName); err == nil { + parentName = id + } + parent := runtime.containerGraph.Get(parentName) + if parent == nil { + return fmt.Errorf("Could not get container for %s", parentName) + } + if id, err := runtime.idIndex.Get(childName); err == nil { + childName = id + } + child := runtime.containerGraph.Get(childName) + if child == nil { + return fmt.Errorf("Could not get container for %s", childName) + } + cc := runtime.Get(child.ID()) + + _, err := runtime.containerGraph.Set(path.Join(parentName, alias), cc.ID) + return err +} + +// FIXME: harmonize with NewGraph() +func NewRuntime(config *DaemonConfig) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(config) if err != nil { return nil, err } - runtime.Dns = dns if k, err := utils.GetKernelVersion(); err != nil { log.Printf("WARNING: %s\n", err) @@ -447,34 +565,39 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e return runtime, nil } -func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { - runtimeRepo := path.Join(root, "containers") +func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { + runtimeRepo := path.Join(config.GraphPath, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { return nil, err } - g, err := NewGraph(path.Join(root, "graph")) + g, err := NewGraph(path.Join(config.GraphPath, "graph")) if err != nil { return nil, err } - volumes, err := NewGraph(path.Join(root, "volumes")) + volumes, err := NewGraph(path.Join(config.GraphPath, "volumes")) if err != nil { return nil, err } - repositories, err := NewTagStore(path.Join(root, "repositories"), g) + repositories, err := NewTagStore(path.Join(config.GraphPath, "repositories"), g) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) } - if NetworkBridgeIface == "" { - NetworkBridgeIface = DefaultNetworkBridge + if config.BridgeIface == "" { + config.BridgeIface = DefaultNetworkBridge } - netManager, err := newNetworkManager(NetworkBridgeIface) + netManager, err := newNetworkManager(config) if err != nil { return nil, err } + + graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db")) + if err != nil { + return nil, err + } + runtime := &Runtime{ - root: root, repository: runtimeRepo, containers: list.New(), networkManager: netManager, @@ -482,8 +605,9 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { repositories: repositories, idIndex: utils.NewTruncIndex(), capabilities: &Capabilities{}, - autoRestart: autoRestart, volumes: volumes, + config: config, + containerGraph: graph, } if err := runtime.restore(); err != nil { diff --git a/runtime_test.go b/runtime_test.go index 8e7c169bfb..7b788f9cbf 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -43,7 +43,7 @@ func nuke(runtime *Runtime) error { } wg.Wait() runtime.networkManager.Close() - return os.RemoveAll(runtime.root) + return os.RemoveAll(runtime.config.GraphPath) } func cleanup(runtime *Runtime) error { @@ -85,8 +85,6 @@ func init() { log.Fatal("docker tests need to be run as root") } - NetworkBridgeIface = unitTestNetworkBridge - // Setup the base runtime, which will be duplicated for each test. // (no tests are run directly in the base) setupBaseImage() @@ -98,7 +96,12 @@ func init() { func setupBaseImage() { - runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) + config := &DaemonConfig{ + GraphPath: unitTestStoreBase, + AutoRestart: false, + BridgeIface: unitTestNetworkBridge, + } + runtime, err := NewRuntimeFromDirectory(config) if err != nil { log.Fatalf("Unable to create a runtime for tests:", err) } @@ -106,7 +109,6 @@ func setupBaseImage() { // Create the "Server" srv := &Server{ runtime: runtime, - enableCors: false, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } @@ -129,7 +131,6 @@ func spawnGlobalDaemon() { globalRuntime = mkRuntime(log.New(os.Stderr, "", 0)) srv := &Server{ runtime: globalRuntime, - enableCors: false, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } @@ -171,7 +172,7 @@ func TestRuntimeCreate(t *testing.T) { t.Errorf("Expected 0 containers, %v found", len(runtime.List())) } - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, @@ -211,12 +212,12 @@ func TestRuntimeCreate(t *testing.T) { t.Errorf("Exists() returned false for a newly created container") } - // Make sure crete with bad parameters returns an error - if _, err = runtime.Create(&Config{Image: GetTestImage(runtime).ID}); err == nil { + // Make sure create with bad parameters returns an error + if _, _, err = runtime.Create(&Config{Image: GetTestImage(runtime).ID}); err == nil { t.Fatal("Builder.Create should throw an error when Cmd is missing") } - if _, err := runtime.Create( + if _, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{}, @@ -230,30 +231,19 @@ func TestRuntimeCreate(t *testing.T) { Cmd: []string{"/bin/ls"}, PortSpecs: []string{"80"}, } - container, err = runtime.Create(config) + container, _, err = runtime.Create(config) - image, err := runtime.Commit(container, "testrepo", "testtag", "", "", config) + _, err = runtime.Commit(container, "testrepo", "testtag", "", "", config) if err != nil { t.Error(err) } - - _, err = runtime.Create( - &Config{ - Image: image.ID, - PortSpecs: []string{"80000:80"}, - }, - ) - if err == nil { - t.Fatal("Builder.Create should throw an error when PortSpecs is invalid") - } - } func TestDestroy(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create(&Config{ + container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }) @@ -327,6 +317,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, strPort string runtime = mkRuntime(t) port = 5554 + p Port ) for { @@ -340,22 +331,34 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, } else { t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) } - container, err = runtime.Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"sh", "-c", cmd}, - PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, + ep := make(map[Port]struct{}, 1) + p = Port(fmt.Sprintf("%s/%s", strPort, proto)) + ep[p] = struct{}{} + + container, _, err = runtime.Create(&Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"sh", "-c", cmd}, + PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, + ExposedPorts: ep, }) - if container != nil { - break - } if err != nil { nuke(runtime) t.Fatal(err) } + + if container != nil { + break + } t.Logf("Port %v already in use, trying another one", strPort) } - if err := container.Start(&HostConfig{}); err != nil { + hostConfig := &HostConfig{ + PortBindings: make(map[Port][]PortBinding), + } + hostConfig.PortBindings[p] = []PortBinding{ + {}, + } + if err := container.Start(hostConfig); err != nil { nuke(runtime) t.Fatal(err) } @@ -369,7 +372,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, // Even if the state is running, lets give some time to lxc to spawn the process container.WaitTimeout(500 * time.Millisecond) - strPort = container.NetworkSettings.PortMapping[strings.Title(proto)][strPort] + strPort = container.NetworkSettings.Ports[p][0].HostPort return runtime, container, strPort } @@ -501,7 +504,8 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) + runtime1.config.AutoRestart = false + runtime2, err := NewRuntimeFromDirectory(runtime1.config) if err != nil { t.Fatal(err) } @@ -528,3 +532,271 @@ func TestRestore(t *testing.T) { } container2.State.Running = false } + +func TestReloadContainerLinks(t *testing.T) { + runtime1 := mkRuntime(t) + defer nuke(runtime1) + // Create a container with one instance of docker + container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t) + defer runtime1.Destroy(container1) + + // Create a second container meant to be killed + container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t) + defer runtime1.Destroy(container2) + + // Start the container non blocking + hostConfig := &HostConfig{} + if err := container2.Start(hostConfig); err != nil { + t.Fatal(err) + } + h1 := &HostConfig{} + // Add a link to container 2 + h1.Links = []string{utils.TruncateID(container2.ID) + ":first"} + if err := container1.Start(h1); err != nil { + t.Fatal(err) + } + + if !container2.State.Running { + t.Fatalf("Container %v should appear as running but isn't", container2.ID) + } + + if !container1.State.Running { + t.Fatalf("Container %s should appear as running bu isn't", container1.ID) + } + + if len(runtime1.List()) != 2 { + t.Errorf("Expected 2 container, %v found", len(runtime1.List())) + } + + 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 + runtime1.config.AutoRestart = true + runtime2, err := NewRuntimeFromDirectory(runtime1.config) + if err != nil { + t.Fatal(err) + } + defer nuke(runtime2) + if len(runtime2.List()) != 2 { + t.Errorf("Expected 2 container, %v found", len(runtime2.List())) + } + runningCount := 0 + for _, c := range runtime2.List() { + if c.State.Running { + t.Logf("Running container found: %v (%v)", c.ID, c.Path) + runningCount++ + } + } + if runningCount != 2 { + t.Fatalf("Expected 2 container alive, %d found", runningCount) + } + + // Make sure container 2 ( the child of container 1 ) was registered and started first + // with the runtime + first := runtime2.containers.Front() + if first.Value.(*Container).ID != container2.ID { + t.Fatalf("Container 2 %s should be registered first in the runtime", container2.ID) + } + + t.Logf("Number of links: %d", runtime2.containerGraph.Refs("engine")) + // Verify that the link is still registered in the runtime + entity := runtime2.containerGraph.Get(fmt.Sprintf("/%s", container1.ID)) + if entity == nil { + t.Fatal("Entity should not be nil") + } +} + +func TestDefaultContainerName(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + srv := &Server{runtime: runtime} + + config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + shortId, _, err := srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + container := runtime.Get(shortId) + containerID := container.ID + + paths := runtime.containerGraph.RefPaths(containerID) + if paths == nil || len(paths) == 0 { + t.Fatalf("Could not find edges for %s", containerID) + } + edge := paths[0] + if edge.ParentID != "0" { + t.Fatalf("Expected engine got %s", edge.ParentID) + } + if edge.EntityID != containerID { + t.Fatalf("Expected %s got %s", containerID, edge.EntityID) + } + if edge.Name != containerID { + t.Fatalf("Expected %s got %s", containerID, edge.Name) + } +} + +func TestDefaultContainerRename(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + srv := &Server{runtime: runtime} + + config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + shortId, _, err := srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + container := runtime.Get(shortId) + containerID := container.ID + + if err := runtime.RenameLink(fmt.Sprintf("/%s", containerID), "/webapp"); err != nil { + t.Fatal(err) + } + + webapp, err := runtime.GetByName("/webapp") + if err != nil { + t.Fatal(err) + } + + if webapp.ID != container.ID { + t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) + } +} + +func TestLinkChildContainer(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + srv := &Server{runtime: runtime} + + config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + shortId, _, err := srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + container := runtime.Get(shortId) + + if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil { + t.Fatal(err) + } + + webapp, err := runtime.GetByName("/webapp") + if err != nil { + t.Fatal(err) + } + + if webapp.ID != container.ID { + t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) + } + + config, _, _, err = ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + shortId, _, err = srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + + childContainer := runtime.Get(shortId) + if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil { + t.Fatal(err) + } + + if err := runtime.Link("/webapp", "/db", "db"); err != nil { + t.Fatal(err) + } + + // Get the child by it's new name + db, err := runtime.GetByName("/webapp/db") + if err != nil { + t.Fatal(err) + } + if db.ID != childContainer.ID { + t.Fatalf("Expect db id to match container id: %s != %s", db.ID, childContainer.ID) + } +} + +func TestGetAllChildren(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + srv := &Server{runtime: runtime} + + config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + shortId, _, err := srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + container := runtime.Get(shortId) + + if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil { + t.Fatal(err) + } + + webapp, err := runtime.GetByName("/webapp") + if err != nil { + t.Fatal(err) + } + + if webapp.ID != container.ID { + t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) + } + + config, _, _, err = ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + shortId, _, err = srv.ContainerCreate(config) + if err != nil { + t.Fatal(err) + } + + childContainer := runtime.Get(shortId) + if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil { + t.Fatal(err) + } + + if err := runtime.Link("/webapp", "/db", "db"); err != nil { + t.Fatal(err) + } + + children, err := runtime.Children("/webapp") + if err != nil { + t.Fatal(err) + } + + if children == nil { + t.Fatal("Children should not be nil") + } + if len(children) == 0 { + t.Fatal("Children should not be empty") + } + + for key, value := range children { + if key != "/webapp/db" { + t.Fatalf("Expected /webapp/db got %s", key) + } + if value.ID != childContainer.ID { + t.Fatalf("Expected id %s got %s", childContainer.ID, value.ID) + } + } +} diff --git a/server.go b/server.go index 676026579c..4f99c0f1d7 100644 --- a/server.go +++ b/server.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" @@ -114,7 +115,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { } func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { - r, err := registry.NewRegistry(srv.runtime.root, nil, srv.HTTPRequestFactory(nil)) + r, err := registry.NewRegistry(srv.runtime.config.GraphPath, nil, srv.HTTPRequestFactory(nil)) if err != nil { return nil, err } @@ -151,7 +152,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return "", err } - c, err := srv.runtime.Create(config) + c, _, err := srv.runtime.Create(config) if err != nil { return "", err } @@ -369,7 +370,7 @@ func (srv *Server) ContainerChanges(name string) ([]Change, error) { func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers { var foundBefore bool var displayed int - retContainers := []APIContainers{} + out := []APIContainers{} for _, container := range srv.runtime.List() { if !container.State.Running && !all && n == -1 && since == "" && before == "" { @@ -391,23 +392,35 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API break } displayed++ - - c := APIContainers{ - ID: container.ID, - } - c.Image = srv.runtime.repositories.ImageName(container.Image) - c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) - c.Created = container.Created.Unix() - c.Status = container.State.String() - c.Ports = container.NetworkSettings.PortMappingAPI() - if size { - c.SizeRw, c.SizeRootFs = container.GetSize() - } - retContainers = append(retContainers, c) + c := createAPIContainer(container, size, srv.runtime) + out = append(out, c) } - return retContainers + return out } +func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers { + c := APIContainers{ + ID: container.ID, + } + names := []string{} + runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { + if e.ID() == container.ID { + names = append(names, p) + } + return nil + }, -1) + c.Names = names + + c.Image = runtime.repositories.ImageName(container.Image) + c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) + c.Created = container.Created.Unix() + c.Status = container.State.String() + c.Ports = container.NetworkSettings.PortMappingAPI() + if size { + c.SizeRw, c.SizeRootFs = container.GetSize() + } + return c +} func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, config *Config) (string, error) { container := srv.runtime.Get(name) if container == nil { @@ -646,7 +659,7 @@ func (srv *Server) poolRemove(kind, key string) error { } func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string, parallel bool) error { - r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory(metaHeaders)) + r, err := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders)) if err != nil { return err } @@ -855,7 +868,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(localName) - r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.HTTPRequestFactory(metaHeaders)) + r, err2 := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders)) if err2 != nil { return err2 } @@ -920,10 +933,9 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write return nil } -func (srv *Server) ContainerCreate(config *Config) (string, error) { - +func (srv *Server) ContainerCreate(config *Config) (string, []string, error) { if config.Memory != 0 && config.Memory < 524288 { - return "", fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)") + return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)") } if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { @@ -933,7 +945,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { config.MemorySwap = -1 } - container, err := srv.runtime.Create(config) + container, buildWarnings, err := srv.runtime.Create(config) if err != nil { if srv.runtime.graph.IsNotExist(err) { @@ -942,12 +954,12 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { tag = DEFAULTTAG } - return "", fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) + return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) } - return "", err + return "", nil, err } srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) - return container.ShortID(), nil + return container.ShortID(), buildWarnings, nil } func (srv *Server) ContainerRestart(name string, t int) error { @@ -962,7 +974,34 @@ func (srv *Server) ContainerRestart(name string, t int) error { return nil } -func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { +func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) error { + if removeLink { + p := name + if p[0] != '/' { + p = "/" + p + } + parent, n := path.Split(p) + l := len(parent) + if parent[l-1] == '/' { + parent = parent[:l-1] + } + + pe := srv.runtime.containerGraph.Get(parent) + parentContainer := srv.runtime.Get(pe.ID()) + + if parentContainer != nil && parentContainer.activeLinks != nil { + if link, exists := parentContainer.activeLinks[n]; exists { + link.Disable() + } else { + utils.Debugf("Could not find active link for %s", name) + } + } + + if err := srv.runtime.containerGraph.Delete(name); err != nil { + return err + } + return nil + } if container := srv.runtime.Get(name); container != nil { if container.State.Running { return fmt.Errorf("Impossible to remove a running container, please stop it first") @@ -1162,14 +1201,32 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) } func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { - if container := srv.runtime.Get(name); container != nil { - if err := container.Start(hostConfig); err != nil { - return fmt.Errorf("Error starting container %s: %s", name, err) - } - srv.LogEvent("start", container.ShortID(), srv.runtime.repositories.ImageName(container.Image)) - } else { + runtime := srv.runtime + container := runtime.Get(name) + if container == nil { return fmt.Errorf("No such container: %s", name) } + + // Register links + if hostConfig != nil && hostConfig.Links != nil { + for _, l := range hostConfig.Links { + parts, err := parseLink(l) + if err != nil { + return err + } + + childName := parts["name"] + if err := runtime.Link(fmt.Sprintf("/%s", container.ID), childName, parts["alias"]); err != nil { + return err + } + } + } + + if err := container.Start(hostConfig); err != nil { + return fmt.Errorf("Error starting container %s: %s", name, err) + } + srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image)) + return nil } @@ -1321,17 +1378,16 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(config *DaemonConfig) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, autoRestart, dns) + runtime, err := NewRuntime(config) if err != nil { return nil, err } srv := &Server{ runtime: runtime, - enableCors: enableCors, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events @@ -1369,7 +1425,6 @@ func (srv *Server) LogEvent(action, id, from string) { type Server struct { sync.Mutex runtime *Runtime - enableCors bool pullingPool map[string]struct{} pushingPool map[string]struct{} events []utils.JSONMessage diff --git a/server_test.go b/server_test.go index c7bfa415db..ef7b1a9106 100644 --- a/server_test.go +++ b/server_test.go @@ -89,7 +89,7 @@ func TestCreateRm(t *testing.T) { t.Fatal(err) } - id, err := srv.ContainerCreate(config) + id, _, err := srv.ContainerCreate(config) if err != nil { t.Fatal(err) } @@ -98,7 +98,7 @@ func TestCreateRm(t *testing.T) { t.Errorf("Expected 1 container, %v found", len(runtime.List())) } - if err = srv.ContainerDestroy(id, true); err != nil { + if err = srv.ContainerDestroy(id, true, false); err != nil { t.Fatal(err) } @@ -119,7 +119,7 @@ func TestCreateRmVolumes(t *testing.T) { t.Fatal(err) } - id, err := srv.ContainerCreate(config) + id, _, err := srv.ContainerCreate(config) if err != nil { t.Fatal(err) } @@ -138,7 +138,7 @@ func TestCreateRmVolumes(t *testing.T) { t.Fatal(err) } - if err = srv.ContainerDestroy(id, true); err != nil { + if err = srv.ContainerDestroy(id, true, false); err != nil { t.Fatal(err) } @@ -158,7 +158,7 @@ func TestCommit(t *testing.T) { t.Fatal(err) } - id, err := srv.ContainerCreate(config) + id, _, err := srv.ContainerCreate(config) if err != nil { t.Fatal(err) } @@ -179,7 +179,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - id, err := srv.ContainerCreate(config) + id, _, err := srv.ContainerCreate(config) if err != nil { t.Fatal(err) } @@ -209,7 +209,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { } // FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty") - if err := srv.ContainerDestroy(id, true); err != nil { + if err := srv.ContainerDestroy(id, true, false); err != nil { t.Fatal(err) } @@ -224,7 +224,7 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) { defer nuke(runtime) // Try to create a container with a memory limit of 1 byte less than the minimum allowed limit. - if _, err := (*Server).ContainerCreate(&Server{runtime: runtime}, + if _, _, err := (*Server).ContainerCreate(&Server{runtime: runtime}, &Config{ Image: GetTestImage(runtime).ID, Memory: 524287, @@ -397,7 +397,7 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - containerID, err := srv.ContainerCreate(config) + containerID, _, err := srv.ContainerCreate(config) if err != nil { t.Fatal(err) } @@ -418,7 +418,7 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - containerID, err = srv.ContainerCreate(config) + containerID, _, err = srv.ContainerCreate(config) if err != nil { t.Fatal(err) } diff --git a/sorter.go b/sorter.go index a818841486..ad931ebcb1 100644 --- a/sorter.go +++ b/sorter.go @@ -34,3 +34,72 @@ func sortImagesByCreationAndTag(images []APIImages) { sort.Sort(sorter) } + +type portSorter struct { + ports []Port + by func(i, j Port) bool +} + +func (s *portSorter) Len() int { + return len(s.ports) +} + +func (s *portSorter) Swap(i, j int) { + s.ports[i], s.ports[j] = s.ports[j], s.ports[i] +} + +func (s *portSorter) Less(i, j int) bool { + ip := s.ports[i] + jp := s.ports[j] + + return s.by(ip, jp) +} + +func sortPorts(ports []Port, predicate func(i, j Port) bool) { + s := &portSorter{ports, predicate} + sort.Sort(s) +} + +type containerSorter struct { + containers []*Container + by func(i, j *Container) bool +} + +func (s *containerSorter) Len() int { + return len(s.containers) +} + +func (s *containerSorter) Swap(i, j int) { + s.containers[i], s.containers[j] = s.containers[j], s.containers[i] +} + +func (s *containerSorter) Less(i, j int) bool { + return s.by(s.containers[i], s.containers[j]) +} + +func sortContainers(containers []*Container, predicate func(i, j *Container) bool) { + s := &containerSorter{containers, predicate} + sort.Sort(s) +} + +type apiLinkSorter struct { + links []APILink + by func(i, j APILink) bool +} + +func (s *apiLinkSorter) Len() int { + return len(s.links) +} + +func (s *apiLinkSorter) Swap(i, j int) { + s.links[i], s.links[j] = s.links[j], s.links[i] +} + +func (s *apiLinkSorter) Less(i, j int) bool { + return s.by(s.links[i], s.links[j]) +} + +func sortLinks(links []APILink, predicate func(i, j APILink) bool) { + s := &apiLinkSorter{links, predicate} + sort.Sort(s) +} diff --git a/sorter_test.go b/sorter_test.go index 5519708ece..d61b1a7112 100644 --- a/sorter_test.go +++ b/sorter_test.go @@ -1,6 +1,7 @@ package docker import ( + "fmt" "testing" ) @@ -55,3 +56,38 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) { t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.") } } + +func TestSortUniquePorts(t *testing.T) { + ports := []Port{ + Port("6379/tcp"), + Port("22/tcp"), + } + + sortPorts(ports, func(ip, jp Port) bool { + return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") + }) + + first := ports[0] + if fmt.Sprint(first) != "22/tcp" { + t.Log(fmt.Sprint(first)) + t.Fail() + } +} + +func TestSortSamePortWithDifferentProto(t *testing.T) { + ports := []Port{ + Port("8888/tcp"), + Port("8888/udp"), + Port("6379/tcp"), + Port("6379/udp"), + } + + sortPorts(ports, func(ip, jp Port) bool { + return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") + }) + + first := ports[0] + if fmt.Sprint(first) != "6379/tcp" { + t.Fail() + } +} diff --git a/utils.go b/utils.go index 8d821e87ff..45949cb350 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,8 @@ package docker import ( "fmt" + "github.com/dotcloud/docker/utils" + "strconv" "strings" ) @@ -27,6 +29,7 @@ func CompareConfig(a, b *Config) bool { len(a.Dns) != len(b.Dns) || len(a.Env) != len(b.Env) || len(a.PortSpecs) != len(b.PortSpecs) || + len(a.ExposedPorts) != len(b.ExposedPorts) || len(a.Entrypoint) != len(b.Entrypoint) || len(a.Volumes) != len(b.Volumes) { return false @@ -52,6 +55,11 @@ func CompareConfig(a, b *Config) bool { return false } } + for k := range a.ExposedPorts { + if _, exists := b.ExposedPorts[k]; !exists { + return false + } + } for i := 0; i < len(a.Entrypoint); i++ { if a.Entrypoint[i] != b.Entrypoint[i] { return false @@ -78,26 +86,38 @@ func MergeConfig(userConf, imageConf *Config) error { if userConf.CpuShares == 0 { userConf.CpuShares = imageConf.CpuShares } - if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 { - userConf.PortSpecs = imageConf.PortSpecs - } else { - for _, imagePortSpec := range imageConf.PortSpecs { - found := false - imageNat, err := parseNat(imagePortSpec) - if err != nil { - return err + if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 { + userConf.ExposedPorts = imageConf.ExposedPorts + } + + if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { + if userConf.ExposedPorts == nil { + userConf.ExposedPorts = make(map[Port]struct{}) + } + ports, _, err := parsePortSpecs(userConf.PortSpecs) + if err != nil { + return err + } + for port := range ports { + if _, exists := userConf.ExposedPorts[port]; !exists { + userConf.ExposedPorts[port] = struct{}{} } - for _, userPortSpec := range userConf.PortSpecs { - userNat, err := parseNat(userPortSpec) - if err != nil { - return err - } - if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend { - found = true - } - } - if !found { - userConf.PortSpecs = append(userConf.PortSpecs, imagePortSpec) + } + userConf.PortSpecs = nil + } + if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 { + utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) + if userConf.ExposedPorts == nil { + userConf.ExposedPorts = make(map[Port]struct{}) + } + + ports, _, err := parsePortSpecs(imageConf.PortSpecs) + if err != nil { + return err + } + for port := range ports { + if _, exists := userConf.ExposedPorts[port]; !exists { + userConf.ExposedPorts[port] = struct{}{} } } } @@ -174,3 +194,98 @@ func parseLxcOpt(opt string) (string, string, error) { } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } + +// We will receive port specs in the format of ip:public:private/proto and these need to be +// parsed in the internal types +func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { + exposedPorts := make(map[Port]struct{}, len(ports)) + bindings := make(map[Port][]PortBinding) + + for _, rawPort := range ports { + proto := "tcp" + if i := strings.LastIndex(rawPort, "/"); i != -1 { + proto = rawPort[i+1:] + rawPort = rawPort[:i] + } + if !strings.Contains(rawPort, ":") { + rawPort = fmt.Sprintf("::%s", rawPort) + } else if len(strings.Split(rawPort, ":")) == 2 { + rawPort = fmt.Sprintf(":%s", rawPort) + } + + parts, err := utils.PartParser("ip:hostPort:containerPort", rawPort) + if err != nil { + return nil, nil, err + } + containerPort := parts["containerPort"] + rawIp := parts["ip"] + hostPort := parts["hostPort"] + + if containerPort == "" { + return nil, nil, fmt.Errorf("No port specified: %s", rawPort) + } + + port := NewPort(proto, containerPort) + if _, exists := exposedPorts[port]; !exists { + exposedPorts[port] = struct{}{} + } + + binding := PortBinding{ + HostIp: rawIp, + HostPort: hostPort, + } + bslice, exists := bindings[port] + if !exists { + bslice = []PortBinding{} + } + bindings[port] = append(bslice, binding) + } + return exposedPorts, bindings, nil +} + +// Splits a port in the format of port/proto +func splitProtoPort(rawPort string) (string, string) { + parts := strings.Split(rawPort, "/") + l := len(parts) + if l == 0 { + return "", "" + } + if l == 1 { + return "tcp", rawPort + } + return parts[0], parts[1] +} + +func parsePort(rawPort string) (int, error) { + port, err := strconv.ParseUint(rawPort, 10, 16) + if err != nil { + return 0, err + } + return int(port), nil +} + +func migratePortMappings(config *Config) error { + if config.PortSpecs != nil { + // We don't have to worry about migrating the bindings to the host + // This is our breaking change + ports, _, err := parsePortSpecs(config.PortSpecs) + if err != nil { + return err + } + config.PortSpecs = nil + + if config.ExposedPorts == nil { + config.ExposedPorts = make(map[Port]struct{}, len(ports)) + } + for k, v := range ports { + config.ExposedPorts[k] = v + } + } + return nil +} + +// Links come in the format of +// name:alias +func parseLink(rawLink string) (map[string]string, error) { + return utils.PartParser("name:alias", rawLink) +} diff --git a/utils/utils.go b/utils/utils.go index 389203b985..0654874089 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1044,3 +1044,22 @@ func IsClosedError(err error) bool { */ return strings.HasSuffix(err.Error(), "use of closed network connection") } + +func PartParser(template, data string) (map[string]string, error) { + // ip:public:private + templateParts := strings.Split(template, ":") + parts := strings.Split(data, ":") + if len(parts) != len(templateParts) { + return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template) + } + out := make(map[string]string, len(templateParts)) + + for i, t := range templateParts { + value := "" + if len(parts) > i { + value = parts[i] + } + out[t] = value + } + return out, nil +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 0c775dbb0b..49f19bf759 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -424,3 +424,23 @@ func TestDependencyGraph(t *testing.T) { t.Fatalf("Expected [d], found %v instead", res[2]) } } + +func TestParsePortMapping(t *testing.T) { + data, err := PartParser("ip:public:private", "192.168.1.1:80:8080") + if err != nil { + t.Fatal(err) + } + + if len(data) != 3 { + t.FailNow() + } + if data["ip"] != "192.168.1.1" { + t.Fail() + } + if data["public"] != "80" { + t.Fail() + } + if data["private"] != "8080" { + t.Fail() + } +} diff --git a/utils_test.go b/utils_test.go index 3f99de570b..d9efd90397 100644 --- a/utils_test.go +++ b/utils_test.go @@ -66,7 +66,11 @@ func newTestRuntime(prefix string) (runtime *Runtime, err error) { return nil, err } - runtime, err = NewRuntimeFromDirectory(root, false) + config := &DaemonConfig{ + GraphPath: root, + AutoRestart: false, + } + runtime, err = NewRuntimeFromDirectory(config) if err != nil { return nil, err } @@ -125,7 +129,7 @@ func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConf if config.Image == "_" { config.Image = GetTestImage(r).ID } - c, err := r.Create(config) + c, _, err := r.Create(config) if err != nil { return nil, nil, err } @@ -253,12 +257,12 @@ func TestMergeConfig(t *testing.T) { } } - if len(configUser.PortSpecs) != 3 { - t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs)) + if len(configUser.ExposedPorts) != 3 { + t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs)) } - for _, portSpecs := range configUser.PortSpecs { - if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" { - t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs) + for portSpecs := range configUser.ExposedPorts { + if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { + t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs) } } if len(configUser.Env) != 3 { @@ -284,48 +288,6 @@ func TestMergeConfig(t *testing.T) { } } -func TestMergeConfigPublicPortNotHonored(t *testing.T) { - volumesImage := make(map[string]struct{}) - volumesImage["/test1"] = struct{}{} - volumesImage["/test2"] = struct{}{} - configImage := &Config{ - Dns: []string{"1.1.1.1", "2.2.2.2"}, - PortSpecs: []string{"1111", "2222"}, - Env: []string{"VAR1=1", "VAR2=2"}, - Volumes: volumesImage, - } - - volumesUser := make(map[string]struct{}) - volumesUser["/test3"] = struct{}{} - configUser := &Config{ - Dns: []string{"3.3.3.3"}, - PortSpecs: []string{"1111:3333"}, - Env: []string{"VAR2=3", "VAR3=3"}, - Volumes: volumesUser, - } - - MergeConfig(configUser, configImage) - - contains := func(a []string, expect string) bool { - for _, p := range a { - if p == expect { - return true - } - } - return false - } - - if !contains(configUser.PortSpecs, "2222") { - t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs) - t.Fail() - } - - if !contains(configUser.PortSpecs, "1111:3333") { - t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs) - t.Fail() - } -} - func TestParseLxcConfOpt(t *testing.T) { opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} @@ -342,3 +304,129 @@ func TestParseLxcConfOpt(t *testing.T) { } } } + +func TestParseNetworkOptsPrivateOnly(t *testing.T) { + ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "tcp" { + t.Logf("Expected tcp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "80" { + t.Logf("Expected 80 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "" { + t.Logf("Expected \"\" got %s", s.HostPort) + t.Fail() + } + if s.HostIp != "192.168.1.100" { + t.Fail() + } + } +} + +func TestParseNetworkOptsPublic(t *testing.T) { + ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "tcp" { + t.Logf("Expected tcp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "80" { + t.Logf("Expected 80 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "8080" { + t.Logf("Expected 8080 got %s", s.HostPort) + t.Fail() + } + if s.HostIp != "192.168.1.100" { + t.Fail() + } + } +} + +func TestParseNetworkOptsUdp(t *testing.T) { + ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "udp" { + t.Logf("Expected udp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "6000" { + t.Logf("Expected 6000 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "" { + t.Logf("Expected \"\" got %s", s.HostPort) + t.Fail() + } + if s.HostIp != "192.168.1.100" { + t.Fail() + } + } +} diff --git a/vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go b/vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go new file mode 100644 index 0000000000..d2fbb62023 --- /dev/null +++ b/vendor/src/code.google.com/p/gosqlite/sqlite/sqlite.go @@ -0,0 +1,404 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sqlite provides access to the SQLite library, version 3. +package sqlite + +/* +#cgo LDFLAGS: -lsqlite3 + +#include +#include + +// These wrappers are necessary because SQLITE_TRANSIENT +// is a pointer constant, and cgo doesn't translate them correctly. +// The definition in sqlite3.h is: +// +// typedef void (*sqlite3_destructor_type)(void*); +// #define SQLITE_STATIC ((sqlite3_destructor_type)0) +// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + +static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { + return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); +} +static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { + return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); +} + +*/ +import "C" + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "time" + "unsafe" +) + +type Errno int + +func (e Errno) Error() string { + s := errText[e] + if s == "" { + return fmt.Sprintf("errno %d", int(e)) + } + return s +} + +var ( + ErrError error = Errno(1) // /* SQL error or missing database */ + ErrInternal error = Errno(2) // /* Internal logic error in SQLite */ + ErrPerm error = Errno(3) // /* Access permission denied */ + ErrAbort error = Errno(4) // /* Callback routine requested an abort */ + ErrBusy error = Errno(5) // /* The database file is locked */ + ErrLocked error = Errno(6) // /* A table in the database is locked */ + ErrNoMem error = Errno(7) // /* A malloc() failed */ + ErrReadOnly error = Errno(8) // /* Attempt to write a readonly database */ + ErrInterrupt error = Errno(9) // /* Operation terminated by sqlite3_interrupt()*/ + ErrIOErr error = Errno(10) // /* Some kind of disk I/O error occurred */ + ErrCorrupt error = Errno(11) // /* The database disk image is malformed */ + ErrFull error = Errno(13) // /* Insertion failed because database is full */ + ErrCantOpen error = Errno(14) // /* Unable to open the database file */ + ErrEmpty error = Errno(16) // /* Database is empty */ + ErrSchema error = Errno(17) // /* The database schema changed */ + ErrTooBig error = Errno(18) // /* String or BLOB exceeds size limit */ + ErrConstraint error = Errno(19) // /* Abort due to constraint violation */ + ErrMismatch error = Errno(20) // /* Data type mismatch */ + ErrMisuse error = Errno(21) // /* Library used incorrectly */ + ErrNolfs error = Errno(22) // /* Uses OS features not supported on host */ + ErrAuth error = Errno(23) // /* Authorization denied */ + ErrFormat error = Errno(24) // /* Auxiliary database format error */ + ErrRange error = Errno(25) // /* 2nd parameter to sqlite3_bind out of range */ + ErrNotDB error = Errno(26) // /* File opened that is not a database file */ + Row = Errno(100) // /* sqlite3_step() has another row ready */ + Done = Errno(101) // /* sqlite3_step() has finished executing */ +) + +var errText = map[Errno]string{ + 1: "SQL error or missing database", + 2: "Internal logic error in SQLite", + 3: "Access permission denied", + 4: "Callback routine requested an abort", + 5: "The database file is locked", + 6: "A table in the database is locked", + 7: "A malloc() failed", + 8: "Attempt to write a readonly database", + 9: "Operation terminated by sqlite3_interrupt()*/", + 10: "Some kind of disk I/O error occurred", + 11: "The database disk image is malformed", + 12: "NOT USED. Table or record not found", + 13: "Insertion failed because database is full", + 14: "Unable to open the database file", + 15: "NOT USED. Database lock protocol error", + 16: "Database is empty", + 17: "The database schema changed", + 18: "String or BLOB exceeds size limit", + 19: "Abort due to constraint violation", + 20: "Data type mismatch", + 21: "Library used incorrectly", + 22: "Uses OS features not supported on host", + 23: "Authorization denied", + 24: "Auxiliary database format error", + 25: "2nd parameter to sqlite3_bind out of range", + 26: "File opened that is not a database file", + 100: "sqlite3_step() has another row ready", + 101: "sqlite3_step() has finished executing", +} + +func (c *Conn) error(rv C.int) error { + if c == nil || c.db == nil { + return errors.New("nil sqlite database") + } + if rv == 0 { + return nil + } + if rv == 21 { // misuse + return Errno(rv) + } + return errors.New(Errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db))) +} + +type Conn struct { + db *C.sqlite3 +} + +func Version() string { + p := C.sqlite3_libversion() + return C.GoString(p) +} + +func Open(filename string) (*Conn, error) { + if C.sqlite3_threadsafe() == 0 { + return nil, errors.New("sqlite library was not compiled for thread-safe operation") + } + + var db *C.sqlite3 + name := C.CString(filename) + defer C.free(unsafe.Pointer(name)) + rv := C.sqlite3_open_v2(name, &db, + C.SQLITE_OPEN_FULLMUTEX| + C.SQLITE_OPEN_READWRITE| + C.SQLITE_OPEN_CREATE, + nil) + if rv != 0 { + return nil, Errno(rv) + } + if db == nil { + return nil, errors.New("sqlite succeeded without returning a database") + } + return &Conn{db}, nil +} + +func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, error) { + dname := C.CString(dstTable) + sname := C.CString(srcTable) + defer C.free(unsafe.Pointer(dname)) + defer C.free(unsafe.Pointer(sname)) + + sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname) + if sb == nil { + return nil, dst.error(C.sqlite3_errcode(dst.db)) + } + return &Backup{sb, dst, src}, nil +} + +type Backup struct { + sb *C.sqlite3_backup + dst, src *Conn +} + +func (b *Backup) Step(npage int) error { + rv := C.sqlite3_backup_step(b.sb, C.int(npage)) + if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked { + return nil + } + return Errno(rv) +} + +type BackupStatus struct { + Remaining int + PageCount int +} + +func (b *Backup) Status() BackupStatus { + return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))} +} + +func (b *Backup) Run(npage int, period time.Duration, c chan<- BackupStatus) error { + var err error + for { + err = b.Step(npage) + if err != nil { + break + } + if c != nil { + c <- b.Status() + } + time.Sleep(period) + } + return b.dst.error(C.sqlite3_errcode(b.dst.db)) +} + +func (b *Backup) Close() error { + if b.sb == nil { + return errors.New("backup already closed") + } + C.sqlite3_backup_finish(b.sb) + b.sb = nil + return nil +} + +func (c *Conn) BusyTimeout(ms int) error { + rv := C.sqlite3_busy_timeout(c.db, C.int(ms)) + if rv == 0 { + return nil + } + return Errno(rv) +} + +func (c *Conn) Exec(cmd string, args ...interface{}) error { + s, err := c.Prepare(cmd) + if err != nil { + return err + } + defer s.Finalize() + err = s.Exec(args...) + if err != nil { + return err + } + rv := C.sqlite3_step(s.stmt) + if Errno(rv) != Done { + return c.error(rv) + } + return nil +} + +type Stmt struct { + c *Conn + stmt *C.sqlite3_stmt + err error + t0 time.Time + sql string + args string +} + +func (c *Conn) Prepare(cmd string) (*Stmt, error) { + if c == nil || c.db == nil { + return nil, errors.New("nil sqlite database") + } + cmdstr := C.CString(cmd) + defer C.free(unsafe.Pointer(cmdstr)) + var stmt *C.sqlite3_stmt + var tail *C.char + rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail) + if rv != 0 { + return nil, c.error(rv) + } + return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Now()}, nil +} + +func (s *Stmt) Exec(args ...interface{}) error { + s.args = fmt.Sprintf(" %v", []interface{}(args)) + rv := C.sqlite3_reset(s.stmt) + if rv != 0 { + return s.c.error(rv) + } + + n := int(C.sqlite3_bind_parameter_count(s.stmt)) + if n != len(args) { + return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n)) + } + + for i, v := range args { + var str string + switch v := v.(type) { + case []byte: + var p *byte + if len(v) > 0 { + p = &v[0] + } + if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 { + return s.c.error(rv) + } + continue + + case bool: + if v { + str = "1" + } else { + str = "0" + } + + default: + str = fmt.Sprint(v) + } + + cstr := C.CString(str) + rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str))) + C.free(unsafe.Pointer(cstr)) + if rv != 0 { + return s.c.error(rv) + } + } + return nil +} + +func (s *Stmt) Error() error { + return s.err +} + +func (s *Stmt) Next() bool { + rv := C.sqlite3_step(s.stmt) + err := Errno(rv) + if err == Row { + return true + } + if err != Done { + s.err = s.c.error(rv) + } + return false +} + +func (s *Stmt) Reset() error { + C.sqlite3_reset(s.stmt) + return nil +} + +func (s *Stmt) Scan(args ...interface{}) error { + n := int(C.sqlite3_column_count(s.stmt)) + if n != len(args) { + return errors.New(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n)) + } + + for i, v := range args { + n := C.sqlite3_column_bytes(s.stmt, C.int(i)) + p := C.sqlite3_column_blob(s.stmt, C.int(i)) + if p == nil && n > 0 { + return errors.New("got nil blob") + } + var data []byte + if n > 0 { + data = (*[1 << 30]byte)(unsafe.Pointer(p))[0:n] + } + switch v := v.(type) { + case *[]byte: + *v = data + case *string: + *v = string(data) + case *bool: + *v = string(data) == "1" + case *int: + x, err := strconv.Atoi(string(data)) + if err != nil { + return errors.New("arg " + strconv.Itoa(i) + " as int: " + err.Error()) + } + *v = x + case *int64: + x, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return errors.New("arg " + strconv.Itoa(i) + " as int64: " + err.Error()) + } + *v = x + case *float64: + x, err := strconv.ParseFloat(string(data), 64) + if err != nil { + return errors.New("arg " + strconv.Itoa(i) + " as float64: " + err.Error()) + } + *v = x + default: + return errors.New("unsupported type in Scan: " + reflect.TypeOf(v).String()) + } + } + return nil +} + +func (s *Stmt) SQL() string { + return s.sql + s.args +} + +func (s *Stmt) Nanoseconds() int64 { + return time.Now().Sub(s.t0).Nanoseconds() +} + +func (s *Stmt) Finalize() error { + rv := C.sqlite3_finalize(s.stmt) + if rv != 0 { + return s.c.error(rv) + } + return nil +} + +func (c *Conn) Close() error { + if c == nil || c.db == nil { + return errors.New("nil sqlite database") + } + rv := C.sqlite3_close(c.db) + if rv != 0 { + return c.error(rv) + } + c.db = nil + return nil +} diff --git a/vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go b/vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go new file mode 100644 index 0000000000..982e08ec04 --- /dev/null +++ b/vendor/src/code.google.com/p/gosqlite/sqlite3/driver.go @@ -0,0 +1,498 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sqlite3 provides access to the SQLite library, version 3. +// +// The package has no exported API. +// It registers a driver for the standard Go database/sql package. +// +// import _ "code.google.com/p/gosqlite/sqlite3" +// +// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.) +package sqlite + +/* +#cgo LDFLAGS: -lsqlite3 + +#include +#include + +// These wrappers are necessary because SQLITE_TRANSIENT +// is a pointer constant, and cgo doesn't translate them correctly. +// The definition in sqlite3.h is: +// +// typedef void (*sqlite3_destructor_type)(void*); +// #define SQLITE_STATIC ((sqlite3_destructor_type)0) +// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) + +static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) { + return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT); +} +static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) { + return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT); +} + +*/ +import "C" + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "io" + "strings" + "time" + "unsafe" +) + +func init() { + sql.Register("sqlite3", impl{}) +} + +type errno int + +func (e errno) Error() string { + s := errText[e] + if s == "" { + return fmt.Sprintf("errno %d", int(e)) + } + return s +} + +var ( + errError error = errno(1) // /* SQL error or missing database */ + errInternal error = errno(2) // /* Internal logic error in SQLite */ + errPerm error = errno(3) // /* Access permission denied */ + errAbort error = errno(4) // /* Callback routine requested an abort */ + errBusy error = errno(5) // /* The database file is locked */ + errLocked error = errno(6) // /* A table in the database is locked */ + errNoMem error = errno(7) // /* A malloc() failed */ + errReadOnly error = errno(8) // /* Attempt to write a readonly database */ + errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/ + errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */ + errCorrupt error = errno(11) // /* The database disk image is malformed */ + errFull error = errno(13) // /* Insertion failed because database is full */ + errCantOpen error = errno(14) // /* Unable to open the database file */ + errEmpty error = errno(16) // /* Database is empty */ + errSchema error = errno(17) // /* The database schema changed */ + errTooBig error = errno(18) // /* String or BLOB exceeds size limit */ + errConstraint error = errno(19) // /* Abort due to constraint violation */ + errMismatch error = errno(20) // /* Data type mismatch */ + errMisuse error = errno(21) // /* Library used incorrectly */ + errNolfs error = errno(22) // /* Uses OS features not supported on host */ + errAuth error = errno(23) // /* Authorization denied */ + errFormat error = errno(24) // /* Auxiliary database format error */ + errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */ + errNotDB error = errno(26) // /* File opened that is not a database file */ + stepRow = errno(100) // /* sqlite3_step() has another row ready */ + stepDone = errno(101) // /* sqlite3_step() has finished executing */ +) + +var errText = map[errno]string{ + 1: "SQL error or missing database", + 2: "Internal logic error in SQLite", + 3: "Access permission denied", + 4: "Callback routine requested an abort", + 5: "The database file is locked", + 6: "A table in the database is locked", + 7: "A malloc() failed", + 8: "Attempt to write a readonly database", + 9: "Operation terminated by sqlite3_interrupt()*/", + 10: "Some kind of disk I/O error occurred", + 11: "The database disk image is malformed", + 12: "NOT USED. Table or record not found", + 13: "Insertion failed because database is full", + 14: "Unable to open the database file", + 15: "NOT USED. Database lock protocol error", + 16: "Database is empty", + 17: "The database schema changed", + 18: "String or BLOB exceeds size limit", + 19: "Abort due to constraint violation", + 20: "Data type mismatch", + 21: "Library used incorrectly", + 22: "Uses OS features not supported on host", + 23: "Authorization denied", + 24: "Auxiliary database format error", + 25: "2nd parameter to sqlite3_bind out of range", + 26: "File opened that is not a database file", + 100: "sqlite3_step() has another row ready", + 101: "sqlite3_step() has finished executing", +} + +type impl struct{} + +func (impl) Open(name string) (driver.Conn, error) { + if C.sqlite3_threadsafe() == 0 { + return nil, errors.New("sqlite library was not compiled for thread-safe operation") + } + + var db *C.sqlite3 + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + rv := C.sqlite3_open_v2(cname, &db, + C.SQLITE_OPEN_FULLMUTEX| + C.SQLITE_OPEN_READWRITE| + C.SQLITE_OPEN_CREATE, + nil) + if rv != 0 { + return nil, errno(rv) + } + if db == nil { + return nil, errors.New("sqlite succeeded without returning a database") + } + return &conn{db: db}, nil +} + +type conn struct { + db *C.sqlite3 + closed bool + tx bool +} + +func (c *conn) error(rv C.int) error { + if rv == 0 { + return nil + } + if rv == 21 || c.closed { + return errno(rv) + } + return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db))) +} + +func (c *conn) Prepare(cmd string) (driver.Stmt, error) { + if c.closed { + panic("database/sql/driver: misuse of sqlite driver: Prepare after Close") + } + cmdstr := C.CString(cmd) + defer C.free(unsafe.Pointer(cmdstr)) + var s *C.sqlite3_stmt + var tail *C.char + rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail) + if rv != 0 { + return nil, c.error(rv) + } + return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil +} + +func (c *conn) Close() error { + if c.closed { + panic("database/sql/driver: misuse of sqlite driver: multiple Close") + } + c.closed = true + rv := C.sqlite3_close(c.db) + c.db = nil + return c.error(rv) +} + +func (c *conn) exec(cmd string) error { + cstring := C.CString(cmd) + defer C.free(unsafe.Pointer(cstring)) + rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil) + return c.error(rv) +} + +func (c *conn) Begin() (driver.Tx, error) { + if c.tx { + panic("database/sql/driver: misuse of sqlite driver: multiple Tx") + } + if err := c.exec("BEGIN TRANSACTION"); err != nil { + return nil, err + } + c.tx = true + return &tx{c}, nil +} + +type tx struct { + c *conn +} + +func (t *tx) Commit() error { + if t.c == nil || !t.c.tx { + panic("database/sql/driver: misuse of sqlite driver: extra Commit") + } + t.c.tx = false + err := t.c.exec("COMMIT TRANSACTION") + t.c = nil + return err +} + +func (t *tx) Rollback() error { + if t.c == nil || !t.c.tx { + panic("database/sql/driver: misuse of sqlite driver: extra Rollback") + } + t.c.tx = false + err := t.c.exec("ROLLBACK") + t.c = nil + return err +} + +type stmt struct { + c *conn + stmt *C.sqlite3_stmt + err error + t0 time.Time + sql string + args string + closed bool + rows bool + colnames []string + coltypes []string +} + +func (s *stmt) Close() error { + if s.rows { + panic("database/sql/driver: misuse of sqlite driver: Close with active Rows") + } + if s.closed { + panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt") + } + s.closed = true + rv := C.sqlite3_finalize(s.stmt) + if rv != 0 { + return s.c.error(rv) + } + return nil +} + +func (s *stmt) NumInput() int { + if s.closed { + panic("database/sql/driver: misuse of sqlite driver: NumInput after Close") + } + return int(C.sqlite3_bind_parameter_count(s.stmt)) +} + +func (s *stmt) reset() error { + return s.c.error(C.sqlite3_reset(s.stmt)) +} + +func (s *stmt) start(args []driver.Value) error { + if err := s.reset(); err != nil { + return err + } + + n := int(C.sqlite3_bind_parameter_count(s.stmt)) + if n != len(args) { + return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n) + } + + for i, v := range args { + var str string + switch v := v.(type) { + case nil: + if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 { + return s.c.error(rv) + } + continue + + case float64: + if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 { + return s.c.error(rv) + } + continue + + case int64: + if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 { + return s.c.error(rv) + } + continue + + case []byte: + var p *byte + if len(v) > 0 { + p = &v[0] + } + if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 { + return s.c.error(rv) + } + continue + + case bool: + var vi int64 + if v { + vi = 1 + } + if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 { + return s.c.error(rv) + } + continue + + case time.Time: + str = v.UTC().Format(timefmt[0]) + + case string: + str = v + + default: + str = fmt.Sprint(v) + } + + cstr := C.CString(str) + rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str))) + C.free(unsafe.Pointer(cstr)) + if rv != 0 { + return s.c.error(rv) + } + } + + return nil +} + +func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { + if s.closed { + panic("database/sql/driver: misuse of sqlite driver: Exec after Close") + } + if s.rows { + panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows") + } + + err := s.start(args) + if err != nil { + return nil, err + } + + rv := C.sqlite3_step(s.stmt) + if errno(rv) != stepDone { + if rv == 0 { + rv = 21 // errMisuse + } + return nil, s.c.error(rv) + } + + id := int64(C.sqlite3_last_insert_rowid(s.c.db)) + rows := int64(C.sqlite3_changes(s.c.db)) + return &result{id, rows}, nil +} + +func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { + if s.closed { + panic("database/sql/driver: misuse of sqlite driver: Query after Close") + } + if s.rows { + panic("database/sql/driver: misuse of sqlite driver: Query with active Rows") + } + + err := s.start(args) + if err != nil { + return nil, err + } + + s.rows = true + if s.colnames == nil { + n := int64(C.sqlite3_column_count(s.stmt)) + s.colnames = make([]string, n) + s.coltypes = make([]string, n) + for i := range s.colnames { + s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i))) + s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i)))) + } + } + return &rows{s}, nil +} + +type rows struct { + s *stmt +} + +func (r *rows) Columns() []string { + if r.s == nil { + panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows") + } + return r.s.colnames +} + +const maxslice = 1<<31 - 1 + +var timefmt = []string{ + "2006-01-02 15:04:05.999999999", + "2006-01-02T15:04:05.999999999", + "2006-01-02 15:04:05", + "2006-01-02T15:04:05", + "2006-01-02 15:04", + "2006-01-02T15:04", + "2006-01-02", +} + +func (r *rows) Next(dst []driver.Value) error { + if r.s == nil { + panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows") + } + + rv := C.sqlite3_step(r.s.stmt) + if errno(rv) != stepRow { + if errno(rv) == stepDone { + return io.EOF + } + if rv == 0 { + rv = 21 + } + return r.s.c.error(rv) + } + + for i := range dst { + switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ { + default: + return fmt.Errorf("unexpected sqlite3 column type %d", typ) + case C.SQLITE_INTEGER: + val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i))) + switch r.s.coltypes[i] { + case "timestamp", "datetime": + dst[i] = time.Unix(val, 0).UTC() + case "boolean": + dst[i] = val > 0 + default: + dst[i] = val + } + + case C.SQLITE_FLOAT: + dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i))) + + case C.SQLITE_BLOB, C.SQLITE_TEXT: + n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i))) + var b []byte + if n > 0 { + p := C.sqlite3_column_blob(r.s.stmt, C.int(i)) + b = (*[maxslice]byte)(unsafe.Pointer(p))[:n] + } + dst[i] = b + switch r.s.coltypes[i] { + case "timestamp", "datetime": + dst[i] = time.Time{} + s := string(b) + for _, f := range timefmt { + if t, err := time.Parse(f, s); err == nil { + dst[i] = t + break + } + } + } + + case C.SQLITE_NULL: + dst[i] = nil + } + } + return nil +} + +func (r *rows) Close() error { + if r.s == nil { + panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows") + } + r.s.rows = false + r.s = nil + return nil +} + +type result struct { + id int64 + rows int64 +} + +func (r *result) LastInsertId() (int64, error) { + return r.id, nil +} + +func (r *result) RowsAffected() (int64, error) { + return r.rows, nil +} From 515070d5138c205e65cd029443c84408ad593ef6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 9 Oct 2013 18:28:17 -0700 Subject: [PATCH 02/40] Remove docker ls from docs and cli usage --- commands.go | 1 - docs/sources/examples/linking_into_redis.rst | 16 +--------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/commands.go b/commands.go index 9a8ae57310..f62fdeef20 100644 --- a/commands.go +++ b/commands.go @@ -92,7 +92,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"kill", "Kill a running container"}, {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, - {"ls", "List links for containers"}, {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, {"ps", "List containers"}, {"pull", "Pull an image or a repository from the docker registry server"}, diff --git a/docs/sources/examples/linking_into_redis.rst b/docs/sources/examples/linking_into_redis.rst index 6c9b7a4ffc..1712e62d69 100644 --- a/docs/sources/examples/linking_into_redis.rst +++ b/docs/sources/examples/linking_into_redis.rst @@ -58,20 +58,12 @@ Run the redis container This will run our redis container using the default port of 6379 and using as password to secure our service. Next we will link the redis container to -a new name using ``docker link`` and ``docker ls``. +a new name using ``docker link``. Linking an existing container ----------------------------- -.. code-block:: bash - - docker ls - - NAME ID IMAGE - /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest - - Docker will automatically create an initial link with the container's id but because the is long and not very user friendly we can link the container with a new name. @@ -80,12 +72,6 @@ a new name. docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis - docker ls - - NAME ID IMAGE - /redis 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest - /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 crosbymichael/redis:latest - Now we can reference our running redis service using the friendly name ``/redis``. We can issue all the commands that you would expect; start, stop, attach, using the new name. From 4576d0f802f864db35bab2130180888abe207c8a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 9 Oct 2013 19:44:31 -0700 Subject: [PATCH 03/40] gograph: fix a bug which caused the unicity of (parent, name) to not be enforced --- gograph/gograph.go | 5 +++++ gograph/gograph_test.go | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/gograph/gograph.go b/gograph/gograph.go index 876bd6b58e..28ce74081e 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -22,7 +22,9 @@ const ( CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"), CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id") ); + ` + createEdgeIndices = ` CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name); ` ) @@ -67,6 +69,9 @@ func NewDatabase(dbPath string) (*Database, error) { if _, err := conn.Exec(createEdgeTable); err != nil { return nil, err } + if _, err := conn.Exec(createEdgeIndices); err != nil { + return nil, err + } rollback := func() { conn.Exec("ROLLBACK") diff --git a/gograph/gograph_test.go b/gograph/gograph_test.go index 519b199699..f21d8bb85c 100644 --- a/gograph/gograph_test.go +++ b/gograph/gograph_test.go @@ -59,6 +59,18 @@ func TestSetEntityWithDifferentName(t *testing.T) { } } +func TestSetDuplicateEntity(t *testing.T) { + db := newTestDb(t) + defer destroyTestDb(db) + + if _, err := db.Set("/foo", "42"); err != nil { + t.Fatal(err) + } + if _, err := db.Set("/foo", "43"); err == nil { + t.Fatalf("Creating an entry with a duplciate path did not cause an error") + } +} + func TestCreateChild(t *testing.T) { db := newTestDb(t) defer destroyTestDb(db) From f7a2f0b93746d33da42b892abc6d5e0a05741321 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 9 Oct 2013 19:45:45 -0700 Subject: [PATCH 04/40] Typos and FIXMEs --- gograph/gograph.go | 2 ++ gograph/gograph_test.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gograph/gograph.go b/gograph/gograph.go index 28ce74081e..21f2966e2d 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -104,9 +104,11 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) { return nil, err } defer conn.Close() + // FIXME: is rollback implicit when closing the connection? rollback := func() { conn.Exec("ROLLBACK") } + // FIXME: use exclusive transactions to avoid race conditions if _, err := conn.Exec("BEGIN"); err != nil { return nil, err } diff --git a/gograph/gograph_test.go b/gograph/gograph_test.go index f21d8bb85c..5c12128ca8 100644 --- a/gograph/gograph_test.go +++ b/gograph/gograph_test.go @@ -22,7 +22,7 @@ func destroyTestDb(db *Database) { func TestNewDatabase(t *testing.T) { db := newTestDb(t) if db == nil { - t.Fatal("Datbase should not be nil") + t.Fatal("Database should not be nil") } defer destroyTestDb(db) } From ce965b8c43f91f0c32403cfaadfd4e279421090b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Oct 2013 13:48:22 -0700 Subject: [PATCH 05/40] Add flag for inter-container communication --- config.go | 19 ++++++++++--------- docker/docker.go | 20 +++++++++++--------- network.go | 13 ++++++++++--- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/config.go b/config.go index d321790567..8f2d22c255 100644 --- a/config.go +++ b/config.go @@ -5,13 +5,14 @@ import ( ) type DaemonConfig struct { - Pidfile string - GraphPath string - ProtoAddresses []string - AutoRestart bool - EnableCors bool - Dns []string - EnableIptables bool - BridgeIface string - DefaultIp net.IP + Pidfile string + GraphPath string + ProtoAddresses []string + AutoRestart bool + EnableCors bool + Dns []string + EnableIptables bool + BridgeIface string + DefaultIp net.IP + InterContainerCommunication bool } diff --git a/docker/docker.go b/docker/docker.go index fbde547231..184b687c7c 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -40,6 +40,7 @@ func main() { flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker") flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports") + flInterContainerComm := flag.Bool("enable-container-comm", false, "Enable inter-container communication") flag.Parse() @@ -81,15 +82,16 @@ func main() { ip := net.ParseIP(*flDefaultIp) config := &docker.DaemonConfig{ - Pidfile: *pidfile, - GraphPath: *flGraphPath, - AutoRestart: *flAutoRestart, - EnableCors: *flEnableCors, - Dns: dns, - EnableIptables: *flEnableIptables, - BridgeIface: bridge, - ProtoAddresses: flHosts, - DefaultIp: ip, + Pidfile: *pidfile, + GraphPath: *flGraphPath, + AutoRestart: *flAutoRestart, + EnableCors: *flEnableCors, + Dns: dns, + EnableIptables: *flEnableIptables, + BridgeIface: bridge, + ProtoAddresses: flHosts, + DefaultIp: ip, + InterContainerCommunication: *flInterContainerComm, } if err := daemon(config); err != nil { log.Fatal(err) diff --git a/network.go b/network.go index b480dc1440..c3f3253aee 100644 --- a/network.go +++ b/network.go @@ -165,14 +165,21 @@ func CreateBridgeIface(config *DaemonConfig) error { if output, err := ip("link", "set", config.BridgeIface, "up"); err != nil { return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output) } + if config.EnableIptables { if err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr, "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { return fmt.Errorf("Unable to enable network bridge NAT: %s", err) } - // Prevent inter-container communication by default - if err := iptables.Raw("-A", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"); err != nil { - return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) + + if !config.InterContainerCommunication { + utils.Debugf("Disable inter-container communication") + if err := iptables.Raw("-A", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"); err != nil { + return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) + } + } else { + utils.Debugf("Enable inter-container communication") + iptables.Raw("-D", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP") } } return nil From bf61d41d6cc1c56967396b5609030732954baa2f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 Oct 2013 10:04:06 +0200 Subject: [PATCH 06/40] Add netlink submodule This contains various operations that can replace the use of shelling out to "ip". --- netlink/netlink.go | 548 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 548 insertions(+) create mode 100644 netlink/netlink.go diff --git a/netlink/netlink.go b/netlink/netlink.go new file mode 100644 index 0000000000..239614b5fb --- /dev/null +++ b/netlink/netlink.go @@ -0,0 +1,548 @@ +package netlink + +import ( + "encoding/binary" + "fmt" + "net" + "syscall" + "unsafe" +) + +var nextSeqNr int + +func nativeEndian() binary.ByteOrder { + var x uint32 = 0x01020304 + if *(*byte)(unsafe.Pointer(&x)) == 0x01 { + return binary.BigEndian + } + return binary.LittleEndian +} + +func getSeq() int { + nextSeqNr = nextSeqNr + 1 + return nextSeqNr +} + +func getIpFamily(ip net.IP) int { + if len(ip) <= net.IPv4len { + return syscall.AF_INET + } + if ip.To4() != nil { + return syscall.AF_INET + } + return syscall.AF_INET6 +} + +type NetlinkRequestData interface { + ToWireFormat() []byte +} + +type IfInfomsg struct { + syscall.IfInfomsg +} + +func newIfInfomsg(family int) *IfInfomsg { + msg := &IfInfomsg{} + msg.Family = uint8(family) + msg.Type = uint16(0) + msg.Index = int32(0) + msg.Flags = uint32(0) + msg.Change = uint32(0) + + return msg +} + +func (msg *IfInfomsg) ToWireFormat() []byte { + native := nativeEndian() + + len := syscall.SizeofIfInfomsg + b := make([]byte, len) + b[0] = msg.Family + b[1] = 0 + native.PutUint16(b[2:4], msg.Type) + native.PutUint32(b[4:8], uint32(msg.Index)) + native.PutUint32(b[8:12], msg.Flags) + native.PutUint32(b[12:16], msg.Change) + return b +} + +type IfAddrmsg struct { + syscall.IfAddrmsg +} + +func newIfAddrmsg(family int) *IfAddrmsg { + msg := &IfAddrmsg{} + msg.Family = uint8(family) + msg.Prefixlen = uint8(0) + msg.Flags = uint8(0) + msg.Scope = uint8(0) + msg.Index = uint32(0) + + return msg +} + +func (msg *IfAddrmsg) ToWireFormat() []byte { + native := nativeEndian() + + len := syscall.SizeofIfAddrmsg + b := make([]byte, len) + b[0] = msg.Family + b[1] = msg.Prefixlen + b[2] = msg.Flags + b[3] = msg.Scope + native.PutUint32(b[4:8], msg.Index) + return b +} + +type RtMsg struct { + syscall.RtMsg +} + +func newRtMsg(family int) *RtMsg { + msg := &RtMsg{} + msg.Family = uint8(family) + msg.Table = syscall.RT_TABLE_MAIN + msg.Scope = syscall.RT_SCOPE_UNIVERSE + msg.Protocol = syscall.RTPROT_BOOT + msg.Type = syscall.RTN_UNICAST + + return msg +} + +func (msg *RtMsg) ToWireFormat() []byte { + native := nativeEndian() + + len := syscall.SizeofRtMsg + b := make([]byte, len) + b[0] = msg.Family + b[1] = msg.Dst_len + b[2] = msg.Src_len + b[3] = msg.Tos + b[4] = msg.Table + b[5] = msg.Protocol + b[6] = msg.Scope + b[7] = msg.Type + native.PutUint32(b[8:12], msg.Flags) + return b +} + +func rtaAlignOf(attrlen int) int { + return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1) +} + +type RtAttr struct { + syscall.RtAttr + Data []byte +} + +func newRtAttr(attrType int, data []byte) *RtAttr { + attr := &RtAttr{} + attr.Type = uint16(attrType) + attr.Data = data + + return attr +} + +func (attr *RtAttr) ToWireFormat() []byte { + native := nativeEndian() + + len := syscall.SizeofRtAttr + len(attr.Data) + b := make([]byte, rtaAlignOf(len)) + native.PutUint16(b[0:2], uint16(len)) + native.PutUint16(b[2:4], attr.Type) + for i, d := range attr.Data { + b[4+i] = d + } + + return b +} + +type NetlinkRequest struct { + syscall.NlMsghdr + Data []NetlinkRequestData +} + +func (rr *NetlinkRequest) ToWireFormat() []byte { + native := nativeEndian() + + length := rr.Len + dataBytes := make([][]byte, len(rr.Data)) + for i, data := range rr.Data { + dataBytes[i] = data.ToWireFormat() + length = length + uint32(len(dataBytes[i])) + } + b := make([]byte, length) + native.PutUint32(b[0:4], length) + native.PutUint16(b[4:6], rr.Type) + native.PutUint16(b[6:8], rr.Flags) + native.PutUint32(b[8:12], rr.Seq) + native.PutUint32(b[12:16], rr.Pid) + + i := 16 + for _, data := range dataBytes { + for _, dataByte := range data { + b[i] = dataByte + i = i + 1 + } + } + return b +} + +func (rr *NetlinkRequest) AddData(data NetlinkRequestData) { + rr.Data = append(rr.Data, data) +} + +func newNetlinkRequest(proto, flags int) *NetlinkRequest { + rr := &NetlinkRequest{} + rr.Len = uint32(syscall.NLMSG_HDRLEN) + rr.Type = uint16(proto) + rr.Flags = syscall.NLM_F_REQUEST | uint16(flags) + rr.Seq = uint32(getSeq()) + return rr +} + +type NetlinkSocket struct { + fd int + lsa syscall.SockaddrNetlink +} + +func getNetlinkSocket() (*NetlinkSocket, error) { + fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_ROUTE) + if err != nil { + return nil, err + } + s := &NetlinkSocket{ + fd: fd, + } + s.lsa.Family = syscall.AF_NETLINK + if err := syscall.Bind(fd, &s.lsa); err != nil { + syscall.Close(fd) + return nil, err + } + + return s, nil +} + +func (s *NetlinkSocket) Close() { + syscall.Close(s.fd) +} + +func (s *NetlinkSocket) Send(request *NetlinkRequest) error { + if err := syscall.Sendto(s.fd, request.ToWireFormat(), 0, &s.lsa); err != nil { + return err + } + return nil +} + +func (s *NetlinkSocket) Recieve() ([]syscall.NetlinkMessage, error) { + rb := make([]byte, syscall.Getpagesize()) + nr, _, err := syscall.Recvfrom(s.fd, rb, 0) + if err != nil { + return nil, err + } + if nr < syscall.NLMSG_HDRLEN { + return nil, fmt.Errorf("Got short response from netlink") + } + rb = rb[:nr] + return syscall.ParseNetlinkMessage(rb) +} + +func (s *NetlinkSocket) GetPid() (uint32, error) { + lsa, err := syscall.Getsockname(s.fd) + if err != nil { + return 0, err + } + switch v := lsa.(type) { + case *syscall.SockaddrNetlink: + return v.Pid, nil + } + return 0, fmt.Errorf("Wrong socket type") +} + +func (s *NetlinkSocket) HandleAck(seq uint32) error { + native := nativeEndian() + + pid, err := s.GetPid() + if err != nil { + return err + } + +done: + for { + msgs, err := s.Recieve() + if err != nil { + return err + } + for _, m := range msgs { + if m.Header.Seq != seq { + return fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, seq) + } + if m.Header.Pid != pid { + return fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid) + } + if m.Header.Type == syscall.NLMSG_DONE { + break done + } + if m.Header.Type == syscall.NLMSG_ERROR { + error := int32(native.Uint32(m.Data[0:4])) + if error == 0 { + break done + } + return syscall.Errno(-error) + } + } + } + + return nil +} + +// Add a new default gateway. Identical to: +// ip route add default via $ip +func AddDefaultGw(ip net.IP) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + family := getIpFamily(ip) + + wb := newNetlinkRequest(syscall.RTM_NEWROUTE, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + + msg := newRtMsg(family) + wb.AddData(msg) + + var ipData []byte + if family == syscall.AF_INET { + ipData = ip.To4() + } else { + ipData = ip.To16() + } + + gateway := newRtAttr(syscall.RTA_GATEWAY, ipData) + + wb.AddData(gateway) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) + +} + +// Bring up a particular network interface +func NetworkLinkUp(iface *net.Interface) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Change = syscall.IFF_UP + msg.Flags = syscall.IFF_UP + msg.Index = int32(iface.Index) + wb.AddData(msg) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +// Add an Ip address to an interface. This is identical to: +// ip addr add $ip/$ipNet dev $iface +func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + family := getIpFamily(ip) + + wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + + msg := newIfAddrmsg(family) + msg.Index = uint32(iface.Index) + prefixLen, _ := ipNet.Mask.Size() + msg.Prefixlen = uint8(prefixLen) + wb.AddData(msg) + + var ipData []byte + if family == syscall.AF_INET { + ipData = ip.To4() + } else { + ipData = ip.To16() + } + + localData := newRtAttr(syscall.IFA_LOCAL, ipData) + wb.AddData(localData) + + addrData := newRtAttr(syscall.IFA_ADDRESS, ipData) + wb.AddData(addrData) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +func zeroTerminated(s string) []byte { + bytes := make([]byte, len(s)+1) + for i := 0; i < len(s); i++ { + bytes[i] = s[i] + } + bytes[len(s)] = 0 + return bytes +} + +func nonZeroTerminated(s string) []byte { + bytes := make([]byte, len(s)) + for i := 0; i < len(s); i++ { + bytes[i] = s[i] + } + return bytes +} + +// Add a new network link of a specified type. This is identical to +// running: ip add link $name type $linkType +func NetworkLinkAdd(name string, linkType string) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + wb.AddData(msg) + + nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name)) + wb.AddData(nameData) + + IFLA_INFO_KIND := 1 + + kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType)) + + infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat()) + wb.AddData(infoData) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + +// Returns an array of IPNet for all the currently routed subnets on ipv4 +// This is similar to the first column of "ip route" output +func NetworkGetRoutes() ([]*net.IPNet, error) { + native := nativeEndian() + + s, err := getNetlinkSocket() + if err != nil { + return nil, err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_GETROUTE, syscall.NLM_F_DUMP) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + wb.AddData(msg) + + if err := s.Send(wb); err != nil { + return nil, err + } + + pid, err := s.GetPid() + if err != nil { + return nil, err + } + + res := make([]*net.IPNet, 0) + +done: + for { + msgs, err := s.Recieve() + if err != nil { + return nil, err + } + for _, m := range msgs { + if m.Header.Seq != wb.Seq { + return nil, fmt.Errorf("Wrong Seq nr %d, expected 1", m.Header.Seq) + } + if m.Header.Pid != pid { + return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid) + } + if m.Header.Type == syscall.NLMSG_DONE { + break done + } + if m.Header.Type == syscall.NLMSG_ERROR { + error := int32(native.Uint32(m.Data[0:4])) + if error == 0 { + break done + } + return nil, syscall.Errno(-error) + } + if m.Header.Type != syscall.RTM_NEWROUTE { + continue + } + + var iface *net.Interface = nil + var ipNet *net.IPNet = nil + + msg := (*RtMsg)(unsafe.Pointer(&m.Data[0:syscall.SizeofRtMsg][0])) + + if msg.Flags&syscall.RTM_F_CLONED != 0 { + // Ignore cloned routes + continue + } + + if msg.Table != syscall.RT_TABLE_MAIN { + // Ignore non-main tables + continue + } + + if msg.Family != syscall.AF_INET { + // Ignore non-ipv4 routes + continue + } + + if msg.Dst_len == 0 { + // Ignore default routes + continue + } + + attrs, err := syscall.ParseNetlinkRouteAttr(&m) + if err != nil { + return nil, err + } + for _, attr := range attrs { + switch attr.Attr.Type { + case syscall.RTA_DST: + ip := attr.Value + ipNet = &net.IPNet{ + IP: ip, + Mask: net.CIDRMask(int(msg.Dst_len), 8*len(ip)), + } + case syscall.RTA_OIF: + index := int(native.Uint32(attr.Value[0:4])) + iface, _ = net.InterfaceByIndex(index) + _ = iface + } + } + if ipNet != nil { + res = append(res, ipNet) + } + } + } + + return res, nil +} From 607c1a520e6d39d0f0ee21f1d281931484206b57 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 17 Sep 2013 15:02:12 +0200 Subject: [PATCH 07/40] Use netlink directly instead of /bin/ip in Sysinit The sysinit code only uses /bin/ip to set a default gateway. This is pretty easy to do via netlink directly, so we can avoid the ip dependency. --- sysinit.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sysinit.go b/sysinit.go index c569d57264..bbd68fa75a 100644 --- a/sysinit.go +++ b/sysinit.go @@ -3,8 +3,10 @@ package docker import ( "flag" "fmt" + "github.com/dotcloud/docker/netlink" "github.com/dotcloud/docker/utils" "log" + "net" "os" "os/exec" "strconv" @@ -17,7 +19,14 @@ func setupNetworking(gw string) { if gw == "" { return } - if _, err := ip("route", "add", "default", "via", gw); err != nil { + + ip := net.ParseIP(gw) + if ip == nil { + log.Fatalf("Unable to set up networking, %s is not a valid IP", gw) + return + } + + if err := netlink.AddDefaultGw(ip); err != nil { log.Fatalf("Unable to set up networking: %v", err) } } From 494cd07f722d6bf5f2e69a6d2d729848882e1971 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 10:57:11 +0200 Subject: [PATCH 08/40] network: Stop shelling out to /sbin/ip We have our own netlink version of all the required calls now, so we can just skip the /sbin/ip dependency. --- network.go | 66 +++++++++++++++---------------------------------- network_test.go | 13 +++++----- 2 files changed, 27 insertions(+), 52 deletions(-) diff --git a/network.go b/network.go index c3f3253aee..5ea76daaff 100644 --- a/network.go +++ b/network.go @@ -6,12 +6,11 @@ import ( "fmt" "github.com/dotcloud/docker/iptables" "github.com/dotcloud/docker/proxy" + "github.com/dotcloud/docker/netlink" "github.com/dotcloud/docker/utils" "log" "net" - "os/exec" "strconv" - "strings" "sync" ) @@ -68,42 +67,10 @@ 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 -} - -func checkRouteOverlaps(routes string, dockerNetwork *net.IPNet) error { - utils.Debugf("Routes:\n\n%s", routes) - for _, line := range strings.Split(routes, "\n") { - if strings.Trim(line, "\r\n\t ") == "" || strings.Contains(line, "default") { - continue - } - _, network, err := net.ParseCIDR(strings.Split(line, " ")[0]) - if err != nil { - // is this a mask-less IP address? - if ip := net.ParseIP(strings.Split(line, " ")[0]); ip == nil { - // fail only if it's neither a network nor a mask-less IP address - return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line) - } else { - _, network, err = net.ParseCIDR(ip.String() + "/32") - if err != nil { - return err - } - } - } - if err == nil && network != nil { - if networkOverlaps(dockerNetwork, network) { - return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, line) - } +func checkRouteOverlaps(networks []*net.IPNet, dockerNetwork *net.IPNet) error { + for _, network := range networks { + if networkOverlaps(dockerNetwork, network) { + return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, network) } } return nil @@ -139,7 +106,7 @@ func CreateBridgeIface(config *DaemonConfig) error { if err != nil { return err } - routes, err := ip("route") + routes, err := netlink.NetworkGetRoutes() if err != nil { return err } @@ -155,15 +122,22 @@ func CreateBridgeIface(config *DaemonConfig) error { } utils.Debugf("Creating bridge %s with network %s", config.BridgeIface, ifaceAddr) - if output, err := ip("link", "add", config.BridgeIface, "type", "bridge"); err != nil { - return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output) + if err := netlink.NetworkLinkAdd(config.BridgeIface, "bridge"); err != nil { + return fmt.Errorf("Error creating bridge: %s", err) } - - if output, err := ip("addr", "add", ifaceAddr, "dev", config.BridgeIface); err != nil { - return fmt.Errorf("Unable to add private network: %s (%s)", err, output) + iface, err := net.InterfaceByName(config.BridgeIface) + if err != nil { + return err } - if output, err := ip("link", "set", config.BridgeIface, "up"); err != nil { - return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output) + ipAddr, ipNet, err := net.ParseCIDR(ifaceAddr) + if err != nil { + return err + } + if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil { + return fmt.Errorf("Unable to add private network: %s", err) + } + if err := netlink.NetworkLinkUp(iface); err != nil { + return fmt.Errorf("Unable to start network bridge: %s", err) } if config.EnableIptables { diff --git a/network_test.go b/network_test.go index a0492b4907..7304e205a5 100644 --- a/network_test.go +++ b/network_test.go @@ -277,12 +277,13 @@ func TestNetworkOverlaps(t *testing.T) { } func TestCheckRouteOverlaps(t *testing.T) { - routes := `default via 10.0.2.2 dev eth0 -10.0.2.0 dev eth0 proto kernel scope link src 10.0.2.15 -10.0.3.0/24 dev lxcbr0 proto kernel scope link src 10.0.3.1 -10.0.42.0/24 dev testdockbr0 proto kernel scope link src 10.0.42.1 -172.16.42.0/24 dev docker0 proto kernel scope link src 172.16.42.1 -192.168.142.0/24 dev eth1 proto kernel scope link src 192.168.142.142` + routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} + + routes := []*net.IPNet{} + for _, addr := range routesData { + _, netX, _ := net.ParseCIDR(addr) + routes = append(routes, netX) + } _, netX, _ := net.ParseCIDR("172.16.0.1/24") if err := checkRouteOverlaps(routes, netX); err != nil { From 249f5a65a55ac9f68c65d57a7e7b5ac486060b97 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 18 Sep 2013 10:53:00 +0200 Subject: [PATCH 09/40] Add a network test This just tries pinging first 127.0.0.1 and then 8.8.8.8. We can't ping via a dns name, because libresolv.so is missing from the test image, so dns resolving doesn't work. --- container_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/container_test.go b/container_test.go index b36898e85b..460f5adc8c 100644 --- a/container_test.go +++ b/container_test.go @@ -407,6 +407,44 @@ func TestOutput(t *testing.T) { } } +func TestContainerNetwork(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, err := runtime.Create( + &Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + if err := container.Run(); err != nil { + t.Fatal(err) + } + if container.State.ExitCode != 0 { + t.Errorf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", container.State.ExitCode) + } + + container, err = runtime.Create( + &Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"ping", "-c", "1", "8.8.8.8"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + if err := container.Run(); err != nil { + t.Fatal(err) + } + if container.State.ExitCode != 0 { + t.Errorf("Unexpected ping 8.8.8.8 exit code %d (expected 0)", container.State.ExitCode) + } +} + func TestKillDifferentUser(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) From d063c8d941d366e4815b91c4fa4fc91fc1e1eb23 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 Oct 2013 10:49:18 +0200 Subject: [PATCH 10/40] Move ListOps to utils submodule This will be needed for later use in docker-init without a docker dependency --- commands.go | 12 ------------ container.go | 14 +++++++------- docker/docker.go | 2 +- sysinit.go | 4 ++-- utils.go | 2 +- utils/utils.go | 12 ++++++++++++ 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/commands.go b/commands.go index f62fdeef20..2a6b59b84c 100644 --- a/commands.go +++ b/commands.go @@ -1480,18 +1480,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error { // Ports type - Used to parse multiple -p flags type ports []int -// ListOpts type -type ListOpts []string - -func (opts *ListOpts) String() string { - return fmt.Sprint(*opts) -} - -func (opts *ListOpts) Set(value string) error { - *opts = append(*opts, value) - return nil -} - // AttachOpts stores arguments to 'docker run -a', eg. which streams to attach to type AttachOpts map[string]bool diff --git a/container.go b/container.go index c6899d2bc5..dfa19ab0c1 100644 --- a/container.go +++ b/container.go @@ -175,30 +175,30 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)") - var flPublish ListOpts + var flPublish utils.ListOpts cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)") - var flExpose ListOpts + var flExpose utils.ListOpts cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host") - var flEnv ListOpts + var flEnv utils.ListOpts cmd.Var(&flEnv, "e", "Set environment variables") - var flDns ListOpts + var flDns utils.ListOpts cmd.Var(&flDns, "dns", "Set custom dns servers") flVolumes := NewPathOpts() cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") - var flVolumesFrom ListOpts + var flVolumesFrom utils.ListOpts cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container") flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image") - var flLxcOpts ListOpts + var flLxcOpts utils.ListOpts cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") - var flLinks ListOpts + var flLinks utils.ListOpts cmd.Var(&flLinks, "link", "Add link to another container (containerid:alias)") if err := cmd.Parse(args); err != nil { diff --git a/docker/docker.go b/docker/docker.go index 184b687c7c..a9c62e5534 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -36,7 +36,7 @@ func main() { 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.") flDns := flag.String("dns", "", "Set custom dns servers") - flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} + flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker") flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports") diff --git a/sysinit.go b/sysinit.go index bbd68fa75a..e93b4787a3 100644 --- a/sysinit.go +++ b/sysinit.go @@ -69,7 +69,7 @@ func changeUser(u string) { } // Clear environment pollution introduced by lxc-start -func cleanupEnv(env ListOpts) { +func cleanupEnv(env utils.ListOpts) { os.Clearenv() for _, kv := range env { parts := strings.SplitN(kv, "=", 2) @@ -104,7 +104,7 @@ func SysInit() { var gw = flag.String("g", "", "gateway address") var workdir = flag.String("w", "", "workdir") - var flEnv ListOpts + var flEnv utils.ListOpts flag.Var(&flEnv, "e", "Set environment variables") flag.Parse() diff --git a/utils.go b/utils.go index 45949cb350..6da1c0aee6 100644 --- a/utils.go +++ b/utils.go @@ -175,7 +175,7 @@ func MergeConfig(userConf, imageConf *Config) error { return nil } -func parseLxcConfOpts(opts ListOpts) ([]KeyValuePair, error) { +func parseLxcConfOpts(opts utils.ListOpts) ([]KeyValuePair, error) { out := make([]KeyValuePair, len(opts)) for i, o := range opts { k, v, err := parseLxcOpt(o) diff --git a/utils/utils.go b/utils/utils.go index 0654874089..3bf2cf05be 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -21,6 +21,18 @@ import ( "time" ) +// ListOpts type +type ListOpts []string + +func (opts *ListOpts) String() string { + return fmt.Sprint(*opts) +} + +func (opts *ListOpts) Set(value string) error { + *opts = append(*opts, value) + return nil +} + // Go is a basic promise implementation: it wraps calls a function in a goroutine, // and returns a channel which will later return the function's return value. func Go(f func() error) chan error { From db999667245926569b161747eed7e08b2ec1c34c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 Oct 2013 10:53:38 +0200 Subject: [PATCH 11/40] Move SysInit to a submodule This allows docker-init to not import the main docker module, which means it won't e.g. pick up any sqlite dependencies. --- docker-init/docker-init.go | 16 ++++++++++++++++ docker/docker.go | 3 ++- runtime_test.go | 3 ++- sysinit.go => sysinit/sysinit.go | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 docker-init/docker-init.go rename sysinit.go => sysinit/sysinit.go (99%) diff --git a/docker-init/docker-init.go b/docker-init/docker-init.go new file mode 100644 index 0000000000..0c363f4ac3 --- /dev/null +++ b/docker-init/docker-init.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/dotcloud/docker/sysinit" +) + +var ( + GITCOMMIT string + VERSION string +) + +func main() { + // Running in init mode + sysinit.SysInit() + return +} diff --git a/docker/docker.go b/docker/docker.go index a9c62e5534..f34c0970e6 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "github.com/dotcloud/docker" + "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "io/ioutil" "log" @@ -23,7 +24,7 @@ var ( func main() { if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { // Running in init mode - docker.SysInit() + sysinit.SysInit() return } // FIXME: Switch d and D ? (to be more sshd like) diff --git a/runtime_test.go b/runtime_test.go index 7b788f9cbf..fa167fd78e 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -3,6 +3,7 @@ package docker import ( "bytes" "fmt" + "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "io" "log" @@ -77,7 +78,7 @@ func init() { // Hack to run sys init during unit testing if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { - SysInit() + sysinit.SysInit() return } diff --git a/sysinit.go b/sysinit/sysinit.go similarity index 99% rename from sysinit.go rename to sysinit/sysinit.go index e93b4787a3..cc1673c8c6 100644 --- a/sysinit.go +++ b/sysinit/sysinit.go @@ -1,4 +1,4 @@ -package docker +package sysinit import ( "flag" From 0e24db3a68cabed1ff8cf8dd10854c5de201329f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 16 Oct 2013 17:08:14 -0700 Subject: [PATCH 12/40] Updated fixes post rebase from master Removed test cases that are no longer applicable with links and the port changes. Remove test case where a test was hitting an external ip. --- api.go | 12 ++++-------- container_test.go | 19 +------------------ runtime.go | 1 - 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/api.go b/api.go index cbaa5ab969..e33cfbf9a7 100644 --- a/api.go +++ b/api.go @@ -74,12 +74,12 @@ func httpError(w http.ResponseWriter, err error) { statusCode = http.StatusUnauthorized } else if strings.Contains(err.Error(), "hasn't been activated") { statusCode = http.StatusForbidden - } - + } + if err != nil { utils.Errorf("HTTP Error: statusCode=%d %s", statusCode, err.Error()) - http.Error(w, err.Error(), statusCode) - } + http.Error(w, err.Error(), statusCode) + } } func writeJSON(w http.ResponseWriter, code int, v interface{}) error { @@ -653,12 +653,8 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r if vars == nil { return fmt.Errorf("Missing parameter") } - var err error name := vars["name"] name = decodeName(name) - if err != nil { - return err - } if err := srv.ContainerStart(name, hostConfig); err != nil { return err } diff --git a/container_test.go b/container_test.go index 460f5adc8c..b8f1f0c8e7 100644 --- a/container_test.go +++ b/container_test.go @@ -410,7 +410,7 @@ func TestOutput(t *testing.T) { func TestContainerNetwork(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := runtime.Create( + container, _, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, @@ -426,23 +426,6 @@ func TestContainerNetwork(t *testing.T) { if container.State.ExitCode != 0 { t.Errorf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", container.State.ExitCode) } - - container, err = runtime.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ping", "-c", "1", "8.8.8.8"}, - }, - ) - if err != nil { - t.Fatal(err) - } - defer runtime.Destroy(container) - if err := container.Run(); err != nil { - t.Fatal(err) - } - if container.State.ExitCode != 0 { - t.Errorf("Unexpected ping 8.8.8.8 exit code %d (expected 0)", container.State.ExitCode) - } } func TestKillDifferentUser(t *testing.T) { diff --git a/runtime.go b/runtime.go index 2b063351fc..565802fa1d 100644 --- a/runtime.go +++ b/runtime.go @@ -324,7 +324,6 @@ func (runtime *Runtime) Create(config *Config) (*Container, []string, error) { if err := MergeConfig(config, img.Config); err != nil { return nil, nil, err } - } if len(config.Entrypoint) != 0 && config.Cmd == nil { From 99a9783abcf5db6ead15228fcf7bc962fff548f5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 17 Oct 2013 17:59:46 +0000 Subject: [PATCH 13/40] fix typo in docs --- docs/sources/examples/linking_into_redis.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/linking_into_redis.rst b/docs/sources/examples/linking_into_redis.rst index 1712e62d69..70a83b4333 100644 --- a/docs/sources/examples/linking_into_redis.rst +++ b/docs/sources/examples/linking_into_redis.rst @@ -54,7 +54,7 @@ Run the redis container .. code-block:: bash - docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass=docker + docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass docker This will run our redis container using the default port of 6379 and using as password to secure our service. Next we will link the redis container to From 387aeb78fb1f518a3c8982d73701f5b7faa1c59c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 17 Oct 2013 18:40:38 +0000 Subject: [PATCH 14/40] refactor notrunc in commands.go to add names support --- commands.go | 40 +++++++++++++++------------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/commands.go b/commands.go index 2a6b59b84c..c0a2a921f4 100644 --- a/commands.go +++ b/commands.go @@ -1066,25 +1066,19 @@ func (cli *DockerCli) CmdImages(args ...string) error { out.Tag = "" } + if !*noTrunc { + out.ID = utils.TruncateID(out.ID) + } + if !*quiet { - fmt.Fprintf(w, "%s\t%s\t", out.Repository, out.Tag) - if *noTrunc { - fmt.Fprintf(w, "%s\t", out.ID) - } else { - fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID)) - } - fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", out.Repository, out.Tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0)))) if out.VirtualSize > 0 { fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize)) } else { fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size)) } } else { - if *noTrunc { - fmt.Fprintln(w, out.ID) - } else { - fmt.Fprintln(w, utils.TruncateID(out.ID)) - } + fmt.Fprintln(w, out.ID) } } @@ -1163,17 +1157,17 @@ func (cli *DockerCli) CmdPs(args ...string) error { } for _, out := range outs { - for i := 0; i < len(out.Names); i++ { - out.Names[i] = utils.Trunc(out.Names[i], 10) + if !*noTrunc { + out.ID = utils.TruncateID(out.ID) } - - names := strings.Join(out.Names, ",") if !*quiet { - if *noTrunc { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names) - } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), names) + if !*noTrunc { + out.Command = utils.Trunc(out.Command, 20) + for i := 0; i < len(out.Names); i++ { + out.Names[i] = utils.Trunc(out.Names[i], 10) + } } + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ",")) if *size { if out.SizeRootFs > 0 { fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs)) @@ -1184,11 +1178,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { fmt.Fprint(w, "\n") } } else { - if *noTrunc { - fmt.Fprintln(w, out.ID) - } else { - fmt.Fprintln(w, utils.TruncateID(out.ID)) - } + fmt.Fprintln(w, out.ID) } } From c4923757f1f92c160a34d0a591f6e62d8d94dea3 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 17 Oct 2013 19:43:25 +0000 Subject: [PATCH 15/40] replace == by HasPrefix for names and improve error message --- api.go | 3 +++ gograph/gograph.go | 6 +++--- runtime.go | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index e33cfbf9a7..49e39e9ede 100644 --- a/api.go +++ b/api.go @@ -1088,6 +1088,9 @@ func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *h } if err := srv.runtime.RenameLink(currentName, newName); err != nil { + if strings.HasSuffix(err.Error(), "name are not unique") { + return fmt.Errorf("Conflict, %s already exists", newName) + } return err } diff --git a/gograph/gograph.go b/gograph/gograph.go index 21f2966e2d..38de5f4a59 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -288,7 +288,7 @@ func (db *Database) Delete(name string) error { return err } - if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil { + if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, n+"%"); err != nil { return err } return nil @@ -356,7 +356,7 @@ func (db *Database) Rename(currentName, newName string) error { return err } - rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name) + rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name LIKE ?;", newEdgeName, parent.id, name+"%") if err != nil { return err } @@ -433,7 +433,7 @@ func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMe // Return the entity based on the parent path and name func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity { var id string - if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil { + if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, name+"%").Scan(&id); err != nil { return nil } return &Entity{id} diff --git a/runtime.go b/runtime.go index 565802fa1d..7127f72999 100644 --- a/runtime.go +++ b/runtime.go @@ -518,7 +518,7 @@ func (runtime *Runtime) RenameLink(oldName, newName string) error { // This is not rename but adding a new link for the default name // Strip the leading '/' - if entity.ID() == oldName[1:] { + if strings.HasPrefix(entity.ID(), oldName[1:]) { _, err := runtime.containerGraph.Set(newName, entity.ID()) return err } From 256b7537e332c68d0fa35b60e06726c40ed66312 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 17 Oct 2013 23:22:14 +0000 Subject: [PATCH 16/40] no more name encoding --- api.go | 16 ---------------- commands.go | 35 +++++++++++++++++++---------------- runtime.go | 3 +++ 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/api.go b/api.go index 49e39e9ede..178ef4d353 100644 --- a/api.go +++ b/api.go @@ -15,7 +15,6 @@ import ( "mime" "net" "net/http" - "net/url" "os" "os/exec" "regexp" @@ -156,7 +155,6 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r * } } } - name = decodeName(name) if err := srv.ContainerKill(name, signal); err != nil { return err } @@ -169,7 +167,6 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) if err := srv.ContainerExport(name, w); err != nil { utils.Errorf("%s", err) @@ -580,7 +577,6 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) if err := srv.ContainerRestart(name, t); err != nil { return err } @@ -596,7 +592,6 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) removeVolume, err := getBoolParam(r.Form.Get("v")) if err != nil { @@ -654,7 +649,6 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) if err := srv.ContainerStart(name, hostConfig); err != nil { return err } @@ -675,7 +669,6 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) if err := srv.ContainerStop(name, t); err != nil { return err @@ -689,7 +682,6 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) status, err := srv.ContainerWait(name) if err != nil { @@ -750,7 +742,6 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) c, err := srv.ContainerInspect(name) if err != nil { @@ -823,7 +814,6 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) if _, err := srv.ContainerInspect(name); err != nil { return err @@ -846,7 +836,6 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } name := vars["name"] - name = decodeName(name) container, err := srv.ContainerInspect(name) if err != nil { @@ -1097,11 +1086,6 @@ func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *h return nil } -func decodeName(name string) string { - s, _ := url.QueryUnescape(name) - return s -} - func createRouter(srv *Server, logging bool) (*mux.Router, error) { r := mux.NewRouter() diff --git a/commands.go b/commands.go index c0a2a921f4..55469bcbb0 100644 --- a/commands.go +++ b/commands.go @@ -23,6 +23,7 @@ import ( "os/signal" "path/filepath" "reflect" + "regexp" "runtime" "sort" "strconv" @@ -504,8 +505,7 @@ func (cli *DockerCli) CmdStop(args ...string) error { v.Set("t", strconv.Itoa(*nSeconds)) for _, name := range cmd.Args() { - encName := cleanName(name) - _, _, err := cli.call("POST", "/containers/"+encName+"/stop?"+v.Encode(), nil) + _, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) } else { @@ -530,8 +530,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error { v.Set("t", strconv.Itoa(*nSeconds)) for _, name := range cmd.Args() { - encName := cleanName(name) - _, _, err := cli.call("POST", "/containers/"+encName+"/restart?"+v.Encode(), nil) + _, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) } else { @@ -607,8 +606,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - encName := cleanName(name) - _, _, err := cli.call("POST", "/containers/"+encName+"/start", nil) + _, _, err := cli.call("POST", "/containers/"+name+"/start", nil) if err != nil { if !*attach || !*openStdin { fmt.Fprintf(cli.err, "%s\n", err) @@ -831,8 +829,7 @@ func (cli *DockerCli) CmdRm(args ...string) error { val.Set("link", "1") } for _, name := range cmd.Args() { - encName := cleanName(name) - _, _, err := cli.call("DELETE", "/containers/"+encName+"?"+val.Encode(), nil) + _, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) } else { @@ -854,8 +851,7 @@ func (cli *DockerCli) CmdKill(args ...string) error { } for _, name := range args { - encName := cleanName(name) - _, _, err := cli.call("POST", "/containers/"+encName+"/kill", nil) + _, _, err := cli.call("POST", "/containers/"+name+"/kill", nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) } else { @@ -1363,7 +1359,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error { cmd.Usage() return nil } - name := cleanName(cmd.Arg(0)) + name := cmd.Arg(0) if err := cli.hijack("POST", "/containers/"+name+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out, cli.err, nil); err != nil { return err @@ -1383,7 +1379,6 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return nil } name := cmd.Arg(0) - name = cleanName(name) body, _, err := cli.call("GET", "/containers/"+name+"/json", nil) if err != nil { return err @@ -1799,6 +1794,10 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, params = bytes.NewBuffer(buf) } + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params) if err != nil { return nil, -1, err @@ -1845,6 +1844,11 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader([]byte{}) } + + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in) if err != nil { return err @@ -1901,6 +1905,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h } func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan bool) error { + // fixme: refactor client to support redirect + re := regexp.MustCompile("/+") + path = re.ReplaceAllString(path, "/") req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) if err != nil { @@ -2081,10 +2088,6 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { return c.State.Running, c.State.ExitCode, nil } -func cleanName(name string) string { - return strings.Replace(name, "/", "%252F", -1) -} - func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { var ( isTerminal = false diff --git a/runtime.go b/runtime.go index 7127f72999..25f378bfb6 100644 --- a/runtime.go +++ b/runtime.go @@ -66,6 +66,9 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { // Get looks for a container by the specified ID or name, and returns it. // If the container is not found, or if an error occurs, nil is returned. func (runtime *Runtime) Get(name string) *Container { + if name[0] != '/' { + name = "/" + name + } if c, _ := runtime.GetByName(name); c != nil { return c } From 89fb51f6063467124c8883399fc293faae8d8ef5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 18 Oct 2013 14:15:24 -0700 Subject: [PATCH 17/40] fix merge issue and gofmt --- network.go | 2 +- runtime_test.go | 2 -- server.go | 3 +++ state.go | 10 +++++----- utils_test.go | 6 +++--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/network.go b/network.go index 5ea76daaff..593ed6cab0 100644 --- a/network.go +++ b/network.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/iptables" - "github.com/dotcloud/docker/proxy" "github.com/dotcloud/docker/netlink" + "github.com/dotcloud/docker/proxy" "github.com/dotcloud/docker/utils" "log" "net" diff --git a/runtime_test.go b/runtime_test.go index fa167fd78e..a32f5c2679 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -95,7 +95,6 @@ func init() { startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() } - func setupBaseImage() { config := &DaemonConfig{ GraphPath: unitTestStoreBase, @@ -123,7 +122,6 @@ func setupBaseImage() { } } - func spawnGlobalDaemon() { if globalRuntime != nil { utils.Debugf("Global runtime already exists. Skipping.") diff --git a/server.go b/server.go index 4f99c0f1d7..8e7a31ce9b 100644 --- a/server.go +++ b/server.go @@ -1216,6 +1216,9 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { } childName := parts["name"] + if childName[0] != '/' { + childName = "/" + childName + } if err := runtime.Link(fmt.Sprintf("/%s", container.ID), childName, parts["alias"]); err != nil { return err } diff --git a/state.go b/state.go index 484032be36..af7de9f4da 100644 --- a/state.go +++ b/state.go @@ -9,12 +9,12 @@ import ( type State struct { sync.Mutex - Running bool - Pid int - ExitCode int - StartedAt time.Time + Running bool + Pid int + ExitCode int + StartedAt time.Time FinishedAt time.Time - Ghost bool + Ghost bool } // String returns a human-readable description of the state diff --git a/utils_test.go b/utils_test.go index d9efd90397..917c4c1591 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1,15 +1,15 @@ package docker import ( - "github.com/dotcloud/docker/utils" "fmt" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" "os" "path" + "runtime" "strings" "testing" - "runtime" ) // This file contains utility functions for docker's unit test suite. @@ -26,7 +26,7 @@ func mkRuntime(f Fataler) *Runtime { pc, _, _, _ := runtime.Caller(1) callerLongName := runtime.FuncForPC(pc).Name() parts := strings.Split(callerLongName, ".") - callerShortName := parts[len(parts) - 1] + callerShortName := parts[len(parts)-1] if globalTestID == "" { globalTestID = GenerateID()[:4] } From 6c863b49ef9de8f203c84f7e2e55f33deeb10bdb Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 18 Oct 2013 16:02:08 -0700 Subject: [PATCH 18/40] remove docker ls and add docker link in usage and switch to go1.2rc2 --- Dockerfile | 2 +- commands.go | 38 +------------------------------------- runtime_test.go | 2 +- 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3c858c8989..ff80e4ad9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ run apt-get install -y -q mercurial run apt-get install -y -q build-essential libsqlite3-dev # Install Go -run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C /usr/local -xz +run curl -s https://go.googlecode.com/files/go1.2rc2.src.tar.gz | tar -v -C /usr/local -xz env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std diff --git a/commands.go b/commands.go index 55469bcbb0..6251feb34b 100644 --- a/commands.go +++ b/commands.go @@ -91,6 +91,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"insert", "Insert a file in an image"}, {"inspect", "Return low-level information on a container"}, {"kill", "Kill a running container"}, + {"link", "Link the container with a new name"}, {"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"}, @@ -1184,43 +1185,6 @@ func (cli *DockerCli) CmdPs(args ...string) error { return nil } -func (cli *DockerCli) CmdLs(args ...string) error { - cmd := Subcmd("ls", "", "List links for containers") - flAll := cmd.Bool("a", false, "Show all links") - - if err := cmd.Parse(args); err != nil { - return nil - } - v := url.Values{} - if *flAll { - v.Set("all", "1") - } - - body, _, err := cli.call("GET", "/containers/links?"+v.Encode(), nil) - if err != nil { - return err - } - var links []APILink - if err := json.Unmarshal(body, &links); err != nil { - return err - } - - w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - fmt.Fprintf(w, "NAME\tID\tIMAGE") - fmt.Fprintf(w, "\n") - - sortLinks(links, func(i, j APILink) bool { - return len(i.Path) < len(j.Path) - }) - for _, link := range links { - fmt.Fprintf(w, "%s\t%s\t%s", link.Path, utils.TruncateID(link.ContainerID), link.Image) - fmt.Fprintf(w, "\n") - } - w.Flush() - - return nil -} - func (cli *DockerCli) CmdLink(args ...string) error { cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name") if err := cmd.Parse(args); err != nil { diff --git a/runtime_test.go b/runtime_test.go index a32f5c2679..40da41801e 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -316,7 +316,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, strPort string runtime = mkRuntime(t) port = 5554 - p Port + p Port ) for { From dbe7d67c183d2fcea2748dd09751c79bc9810b88 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 21 Oct 2013 15:42:27 -0700 Subject: [PATCH 19/40] fix tests --- runtime_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/runtime_test.go b/runtime_test.go index 40da41801e..e042ae6e81 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -536,7 +536,7 @@ func TestReloadContainerLinks(t *testing.T) { runtime1 := mkRuntime(t) defer nuke(runtime1) // Create a container with one instance of docker - container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t) + container1, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/sh"}, t) defer runtime1.Destroy(container1) // Create a second container meant to be killed @@ -560,17 +560,13 @@ func TestReloadContainerLinks(t *testing.T) { } if !container1.State.Running { - t.Fatalf("Container %s should appear as running bu isn't", container1.ID) + t.Fatalf("Container %s should appear as running but isn't", container1.ID) } if len(runtime1.List()) != 2 { t.Errorf("Expected 2 container, %v found", len(runtime1.List())) } - 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 runtime1.config.AutoRestart = true From fb8d488896bd147aa810c9736fce1186b8d769b5 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 17 Oct 2013 23:36:28 -0600 Subject: [PATCH 20/40] Add a few small consistency tweaks in hack --- hack/PACKAGERS.md | 9 +++------ hack/make.sh | 1 - hack/make/test | 2 ++ hack/make/ubuntu | 3 +-- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index ab8d74da8f..57ec45fd81 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -40,7 +40,6 @@ To build docker, you will need the following system dependencies * A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces) under the path *src/github.com/dotcloud/docker*. See - ## Go dependencies All Go dependencies are vendored under ./vendor. They are used by the official build, @@ -55,7 +54,6 @@ NOTE: if you''re not able to package the exact version (to the exact commit) of please get in touch so we can remediate! Who knows what discrepancies can be caused by even the slightest deviation. We promise to do our best to make everybody happy. - ## Disabling CGO Make sure to disable CGO on your system, and then recompile the standard library on the build @@ -71,7 +69,7 @@ cd /tmp && echo 'package main' > t.go && go test -a -i -v To build the docker binary, run the following command with the source checkout as the working directory: -``` +```bash ./hack/make.sh binary ``` @@ -82,7 +80,7 @@ You are encouraged to use ./hack/make.sh without modification. If you must absol your own script (are you really, really sure you need to? make.sh is really not that complicated), then please take care the respect the following: -* In *./hack/make.sh*: $LDFLAGS, $VERSION and $GITCOMMIT +* In *./hack/make.sh*: $LDFLAGS, $BUILDFLAGS, $VERSION and $GITCOMMIT * In *./hack/make/binary*: the exact build command to run You may be tempted to tweak these settings. In particular, being a rigorous maintainer, you may want @@ -106,7 +104,6 @@ dependencies to be installed (see below). The test suite will also download a small test container, so you will need internet connectivity. - ## Runtime dependencies To run properly, docker needs the following software to be installed at runtime: @@ -133,6 +130,6 @@ for your distro''s process supervisor of choice. Docker should be run as root, with the following arguments: -``` +```bash docker -d ``` diff --git a/hack/make.sh b/hack/make.sh index 9d960a0efc..903df4a502 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -48,7 +48,6 @@ fi LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' BUILDFLAGS='-tags netgo' - bundle() { bundlescript=$1 bundle=$(basename $bundlescript) diff --git a/hack/make/test b/hack/make/test index c8ed1bc7c0..090e2782c1 100644 --- a/hack/make/test +++ b/hack/make/test @@ -1,3 +1,5 @@ +#!/bin/sh + DEST=$1 set -e diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 26eea0a5cc..5834172fd1 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -3,8 +3,7 @@ DEST=$1 PKGVERSION="$VERSION" -if test -n "$(git status --porcelain)" -then +if [ -n "$(git status --porcelain)" ]; then PKGVERSION="$PKGVERSION-$(date +%Y%m%d%H%M%S)-$GITCOMMIT" fi From d7897508516c05e7ffea5b96053ec80c7771d777 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 17 Oct 2013 23:38:34 -0600 Subject: [PATCH 21/40] Refactor the handling of static-related build flags This is as discussed with @shykes on IRC --- hack/make.sh | 2 +- hack/make/binary | 3 +-- hack/make/test | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index 903df4a502..dd2a96945c 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -45,7 +45,7 @@ if [ -n "$(git status --porcelain)" ]; then fi # Use these flags when compiling the tests and final binary -LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' +LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w' BUILDFLAGS='-tags netgo' bundle() { diff --git a/hack/make/binary b/hack/make/binary index 4c2c248975..4bc0effc43 100644 --- a/hack/make/binary +++ b/hack/make/binary @@ -2,6 +2,5 @@ DEST=$1 -go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker - +go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS -linkmode external -extldflags \"-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files\"" $BUILDFLAGS ./docker echo "Created binary: $DEST/docker-$VERSION" diff --git a/hack/make/test b/hack/make/test index 090e2782c1..eab131f71d 100644 --- a/hack/make/test +++ b/hack/make/test @@ -17,7 +17,7 @@ bundle_test() { set -x cd $test_dir go test -i - go test -v -ldflags "$LDFLAGS" $BUILDFLAGS $TESTFLAGS + go test -v -ldflags "$LDFLAGS -linkmode external -extldflags \"-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files\"" $BUILDFLAGS $TESTFLAGS ) done } 2>&1 | tee $DEST/test.log } From cf86e2bb226c82a991eb392e426e707cf8193c03 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 17 Oct 2013 23:39:57 -0600 Subject: [PATCH 22/40] Rename all cases of "docker-init" to "dockerinit" for consistency --- container.go | 2 +- docker-init/docker-init.go => dockerinit/dockerinit.go | 0 lxc_template.go | 2 +- sysinit/sysinit.go | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename docker-init/docker-init.go => dockerinit/dockerinit.go (100%) diff --git a/container.go b/container.go index dfa19ab0c1..9ee91a4c50 100644 --- a/container.go +++ b/container.go @@ -1119,7 +1119,7 @@ func (container *Container) releaseNetwork() { container.NetworkSettings = &NetworkSettings{} } -// FIXME: replace this with a control socket within docker-init +// FIXME: replace this with a control socket within dockerinit func (container *Container) waitLxc() error { for { output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput() diff --git a/docker-init/docker-init.go b/dockerinit/dockerinit.go similarity index 100% rename from docker-init/docker-init.go rename to dockerinit/dockerinit.go diff --git a/lxc_template.go b/lxc_template.go index 37232a89d3..382c16029d 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -94,7 +94,7 @@ lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,no #lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0 lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 -# Inject docker-init +# Inject dockerinit lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/.dockerinit none bind,ro 0 0 # In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index cc1673c8c6..a37db72423 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -97,7 +97,7 @@ func executeProgram(name string, args []string) { // up the environment before running the actual process func SysInit() { if len(os.Args) <= 1 { - fmt.Println("You should not invoke docker-init manually") + fmt.Println("You should not invoke dockerinit manually") os.Exit(1) } var u = flag.String("u", "", "username or uid") From 21161dbd515fb97cf9052a5980de6fc6180a89dc Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 17 Oct 2013 23:40:41 -0600 Subject: [PATCH 23/40] Add dynbinary and dyntest scripts for building/testing a separate static dockerinit binary After a nice long brainstorming session with @shykes on IRC, we decided on using a SHA1 hash of dockerinit compiled into the dynamic docker binary to ensure that we always use the two in a perfect pair, and never mix and match. --- hack/PACKAGERS.md | 10 +++++++ hack/make.sh | 3 ++ hack/make/binary | 2 +- hack/make/dynbinary | 15 ++++++++++ hack/make/dyntest | 41 +++++++++++++++++++++++++++ hack/make/test | 2 +- runtime.go | 11 ++++---- runtime_test.go | 20 ++++++++++++++ utils/utils.go | 67 +++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 hack/make/dynbinary create mode 100644 hack/make/dyntest diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 57ec45fd81..11d4698f3e 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -90,6 +90,16 @@ You would do the users of your distro a disservice and "void the docker warranty A good comparison is Busybox: all distros package it as a statically linked binary, because it just makes sense. Docker is the same way. +If you *must* have a non-static Docker binary, please use: + +```bash +./hack/make.sh dynbinary +``` + +This will create *./bundles/$VERSION/dynbinary/docker-$VERSION* and *./bundles/$VERSION/binary/dockerinit-$VERSION*. +The first of these would usually be installed at */usr/bin/docker*, while the second must be installed +at */usr/libexec/docker/dockerinit*. + ## Testing Docker Before releasing your binary, make sure to run the tests! Run the following command with the source diff --git a/hack/make.sh b/hack/make.sh index dd2a96945c..7e81417e46 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -35,6 +35,8 @@ grep -q "$RESOLVCONF" /proc/mounts || { DEFAULT_BUNDLES=( binary test + dynbinary + dyntest ubuntu ) @@ -46,6 +48,7 @@ fi # Use these flags when compiling the tests and final binary LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w' +LDFLAGS_STATIC='-X github.com/dotcloud/docker/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' BUILDFLAGS='-tags netgo' bundle() { diff --git a/hack/make/binary b/hack/make/binary index 4bc0effc43..6363c2ab09 100644 --- a/hack/make/binary +++ b/hack/make/binary @@ -2,5 +2,5 @@ DEST=$1 -go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS -linkmode external -extldflags \"-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files\"" $BUILDFLAGS ./docker +go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS ./docker echo "Created binary: $DEST/docker-$VERSION" diff --git a/hack/make/dynbinary b/hack/make/dynbinary new file mode 100644 index 0000000000..e630c94f53 --- /dev/null +++ b/hack/make/dynbinary @@ -0,0 +1,15 @@ +#!/bin/sh + +DEST=$1 + +# dockerinit still needs to be a static binary, even if docker is dynamic +CGO_ENABLED=0 go build -a -o $DEST/dockerinit-$VERSION -ldflags "$LDFLAGS -d" $BUILDFLAGS ./dockerinit +echo "Created binary: $DEST/dockerinit-$VERSION" +ln -sf dockerinit-$VERSION $DEST/dockerinit + +# sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another +export DOCKER_INITSHA1="$(sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)" +# exported so that "dyntest" can easily access it later without recalculating it + +go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS ./docker +echo "Created binary: $DEST/docker-$VERSION" diff --git a/hack/make/dyntest b/hack/make/dyntest new file mode 100644 index 0000000000..848ed8b5cc --- /dev/null +++ b/hack/make/dyntest @@ -0,0 +1,41 @@ +#!/bin/sh + +DEST=$1 +INIT=$DEST/../dynbinary/dockerinit-$VERSION + +set -e + +if [ ! -x "$INIT" ]; then + echo >&2 'error: dynbinary must be run before dyntest' + false +fi + +# Run Docker's test suite, including sub-packages, and store their output as a bundle +# If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'. +# You can use this to select certain tests to run, eg. +# +# TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test +# +bundle_test() { + { + date + for test_dir in $(find_test_dirs); do ( + set -x + cd $test_dir + export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION + go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS + ) done + } 2>&1 | tee $DEST/test.log +} + + +# This helper function walks the current directory looking for directories +# holding Go test files, and prints their paths on standard output, one per +# line. +find_test_dirs() { + find . -name '*_test.go' | grep -v '^./vendor' | + { while read f; do dirname $f; done; } | + sort -u +} + +bundle_test diff --git a/hack/make/test b/hack/make/test index eab131f71d..f905fa14a5 100644 --- a/hack/make/test +++ b/hack/make/test @@ -17,7 +17,7 @@ bundle_test() { set -x cd $test_dir go test -i - go test -v -ldflags "$LDFLAGS -linkmode external -extldflags \"-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files\"" $BUILDFLAGS $TESTFLAGS + go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS ) done } 2>&1 | tee $DEST/test.log } diff --git a/runtime.go b/runtime.go index 25f378bfb6..0a492cd77e 100644 --- a/runtime.go +++ b/runtime.go @@ -38,12 +38,6 @@ type Runtime struct { containerGraph *gograph.Database } -var sysInitPath string - -func init() { - sysInitPath = utils.SelfPath() -} - // List returns an array of all containers registered in the runtime. func (runtime *Runtime) List() []*Container { containers := new(History) @@ -335,6 +329,11 @@ func (runtime *Runtime) Create(config *Config) (*Container, []string, error) { return nil, nil, fmt.Errorf("No command specified") } + sysInitPath := utils.DockerInitPath() + if sysInitPath == "" { + return nil, nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.") + } + // Generate id id := GenerateID() diff --git a/runtime_test.go b/runtime_test.go index e042ae6e81..0aa2d3d543 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -9,6 +9,7 @@ import ( "log" "net" "os" + "path/filepath" "runtime" "strconv" "strings" @@ -86,6 +87,25 @@ func init() { log.Fatal("docker tests need to be run as root") } + // Copy dockerinit into our current testing directory, if provided (so we can test a separate dockerinit binary) + if dockerinit := os.Getenv("TEST_DOCKERINIT_PATH"); dockerinit != "" { + src, err := os.Open(dockerinit) + if err != nil { + log.Fatalf("Unable to open TEST_DOCKERINIT_PATH: %s\n", err) + } + defer src.Close() + dst, err := os.OpenFile(filepath.Join(filepath.Dir(utils.SelfPath()), "dockerinit"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0555) + if err != nil { + log.Fatalf("Unable to create dockerinit in test directory: %s\n", err) + } + defer dst.Close() + if _, err := io.Copy(dst, src); err != nil { + log.Fatalf("Unable to copy dockerinit to TEST_DOCKERINIT_PATH: %s\n", err) + } + dst.Close() + src.Close() + } + // Setup the base runtime, which will be duplicated for each test. // (no tests are run directly in the base) setupBaseImage() diff --git a/utils/utils.go b/utils/utils.go index 3bf2cf05be..d53094397f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,6 +2,7 @@ package utils import ( "bytes" + "crypto/sha1" "crypto/sha256" "encoding/hex" "encoding/json" @@ -21,6 +22,11 @@ import ( "time" ) +var ( + IAMSTATIC bool // whether or not Docker itself was compiled statically via ./hack/make.sh binary + INITSHA1 string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary +) + // ListOpts type type ListOpts []string @@ -190,6 +196,67 @@ func SelfPath() string { return path } +func dockerInitSha1(target string) string { + f, err := os.Open(target) + if err != nil { + return "" + } + defer f.Close() + h := sha1.New() + _, err = io.Copy(h, f) + if err != nil { + return "" + } + return hex.EncodeToString(h.Sum(nil)) +} + +func isValidDockerInitPath(target string, selfPath string) bool { // target and selfPath should be absolute (InitPath and SelfPath already do this) + if IAMSTATIC { + if target == selfPath { + return true + } + targetFileInfo, err := os.Lstat(target) + if err != nil { + return false + } + selfPathFileInfo, err := os.Lstat(selfPath) + if err != nil { + return false + } + return os.SameFile(targetFileInfo, selfPathFileInfo) + } + return INITSHA1 != "" && dockerInitSha1(target) == INITSHA1 +} + +// Figure out the path of our dockerinit (which may be SelfPath()) +func DockerInitPath() string { + selfPath := SelfPath() + if isValidDockerInitPath(selfPath, selfPath) { + // if we're valid, don't bother checking anything else + return selfPath + } + var possibleInits = []string{ + filepath.Join(filepath.Dir(selfPath), "dockerinit"), + // "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec." + "/usr/libexec/docker/dockerinit", + "/usr/local/libexec/docker/dockerinit", + } + for _, dockerInit := range possibleInits { + path, err := exec.LookPath(dockerInit) + if err == nil { + path, err = filepath.Abs(path) + if err != nil { + // LookPath already validated that this file exists and is executable (following symlinks), so how could Abs fail? + panic(err) + } + if isValidDockerInitPath(path, selfPath) { + return path + } + } + } + return "" +} + type NopWriter struct{} func (*NopWriter) Write(buf []byte) (int, error) { From 7072d2aacab5cd85ed78298b1a0c3ca3b68ed30c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 22 Oct 2013 14:54:04 -0700 Subject: [PATCH 24/40] Close connection in Get method --- gograph/gograph.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gograph/gograph.go b/gograph/gograph.go index 38de5f4a59..38ad71082b 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -166,6 +166,8 @@ func (db *Database) Get(name string) *Entity { if err != nil { return nil } + defer conn.Close() + e, err := db.get(conn, name) if err != nil { return nil From 7c08aeeba4ab94f77e221bfc01741cce0866d224 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 22 Oct 2013 16:23:52 -0700 Subject: [PATCH 25/40] Use persistent connection for links database Add close method to Runtime and Server to make sure that any underlying connections are cleaned up --- docker/docker.go | 14 ++- gograph/gograph.go | 195 +++++++++++++++------------------------- gograph/gograph_test.go | 95 ++++++++++---------- runtime.go | 21 ++++- runtime_test.go | 4 +- server.go | 4 + 6 files changed, 156 insertions(+), 177 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index f34c0970e6..362e899ba1 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -148,18 +148,22 @@ func daemon(config *docker.DaemonConfig) error { } defer removePidFile(config.Pidfile) + server, err := docker.NewServer(config) + if err != nil { + return err + } + defer server.Close() + c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) go func() { sig := <-c log.Printf("Received signal '%v', exiting\n", sig) + server.Close() removePidFile(config.Pidfile) os.Exit(0) }() - server, err := docker.NewServer(config) - if err != nil { - return err - } + chErrors := make(chan error, len(config.ProtoAddresses)) for _, protoAddr := range config.ProtoAddresses { protoAddrParts := strings.SplitN(protoAddr, "://", 2) @@ -170,6 +174,8 @@ func daemon(config *docker.DaemonConfig) error { log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } } else { + server.Close() + removePidFile(config.Pidfile) log.Fatal("Invalid protocol format.") } go func() { diff --git a/gograph/gograph.go b/gograph/gograph.go index 38ad71082b..a8af0600cc 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -1,10 +1,8 @@ package gograph import ( - _ "code.google.com/p/gosqlite/sqlite3" "database/sql" "fmt" - "os" "path" ) @@ -25,7 +23,7 @@ const ( ` createEdgeIndices = ` - CREATE UNIQUE INDEX "name_parent_ix" ON "edge" (parent_id, name); + CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name); ` ) @@ -48,74 +46,71 @@ type WalkFunc func(fullPath string, entity *Entity) error // Graph database for storing entities and their relationships type Database struct { - dbPath string + conn *sql.DB } // Create a new graph database initialized with a root entity -func NewDatabase(dbPath string) (*Database, error) { - db := &Database{dbPath} - if _, err := os.Stat(dbPath); err == nil { - return db, nil +func NewDatabase(conn *sql.DB, init bool) (*Database, error) { + if conn == nil { + return nil, fmt.Errorf("Database connection cannot be nil") } - conn, err := db.openConn() - if err != nil { - return nil, err - } - defer conn.Close() + db := &Database{conn} - if _, err := conn.Exec(createEntityTable); err != nil { - return nil, err - } - if _, err := conn.Exec(createEdgeTable); err != nil { - return nil, err - } - if _, err := conn.Exec(createEdgeIndices); err != nil { - return nil, err - } + if init { + if _, err := conn.Exec(createEntityTable); err != nil { + return nil, err + } + if _, err := conn.Exec(createEdgeTable); err != nil { + return nil, err + } + if _, err := conn.Exec(createEdgeIndices); err != nil { + return nil, err + } - rollback := func() { - conn.Exec("ROLLBACK") - } + rollback := func() { + conn.Exec("ROLLBACK") + } - // Create root entities - if _, err := conn.Exec("BEGIN"); err != nil { - return nil, err - } - if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil { - rollback() - return nil, err - } + // Create root entities + if _, err := conn.Exec("BEGIN"); err != nil { + return nil, err + } + if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil { + rollback() + return nil, err + } - if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil { - rollback() - return nil, err - } + if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil { + rollback() + return nil, err + } - if _, err := conn.Exec("COMMIT"); err != nil { - return nil, err + if _, err := conn.Exec("COMMIT"); err != nil { + return nil, err + } } return db, nil } +// Close the underlying connection to the database +func (db *Database) Close() error { + return db.conn.Close() +} + // Set the entity id for a given path func (db *Database) Set(fullPath, id string) (*Entity, error) { - conn, err := db.openConn() - if err != nil { - return nil, err - } - defer conn.Close() // FIXME: is rollback implicit when closing the connection? rollback := func() { - conn.Exec("ROLLBACK") + db.conn.Exec("ROLLBACK") } // FIXME: use exclusive transactions to avoid race conditions - if _, err := conn.Exec("BEGIN"); err != nil { + if _, err := db.conn.Exec("BEGIN"); err != nil { return nil, err } var entityId string - if err := conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil { + if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil { if err == sql.ErrNoRows { - if _, err := conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil { + if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil { rollback() return nil, err } @@ -127,19 +122,19 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) { e := &Entity{id} parentPath, name := splitPath(fullPath) - if err := db.setEdge(conn, parentPath, name, e); err != nil { + if err := db.setEdge(parentPath, name, e); err != nil { rollback() return nil, err } - if _, err := conn.Exec("COMMIT"); err != nil { + if _, err := db.conn.Exec("COMMIT"); err != nil { return nil, err } return e, nil } -func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) error { - parent, err := db.get(conn, parentPath) +func (db *Database) setEdge(parentPath, name string, e *Entity) error { + parent, err := db.get(parentPath) if err != nil { return err } @@ -147,7 +142,7 @@ func (db *Database) setEdge(conn *sql.DB, parentPath, name string, e *Entity) er return fmt.Errorf("Cannot set self as child") } - if _, err := conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil { + if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil { return err } return nil @@ -162,20 +157,14 @@ func (db *Database) RootEntity() *Entity { // Return the entity for a given path func (db *Database) Get(name string) *Entity { - conn, err := db.openConn() - if err != nil { - return nil - } - defer conn.Close() - - e, err := db.get(conn, name) + e, err := db.get(name) if err != nil { return nil } return e } -func (db *Database) get(conn *sql.DB, name string) (*Entity, error) { +func (db *Database) get(name string) (*Entity, error) { e := db.RootEntity() // We always know the root name so return it if // it is requested @@ -187,7 +176,7 @@ func (db *Database) get(conn *sql.DB, name string) (*Entity, error) { for i := 1; i < len(parts); i++ { p := parts[i] - next := db.child(conn, e, p) + next := db.child(e, p) if next == nil { return nil, fmt.Errorf("Cannot find child") } @@ -201,26 +190,14 @@ func (db *Database) get(conn *sql.DB, name string) (*Entity, error) { // The key will be the full path of the entity func (db *Database) List(name string, depth int) Entities { out := Entities{} - conn, err := db.openConn() - if err != nil { - return out - } - defer conn.Close() - - for c := range db.children(conn, name, depth) { + for c := range db.children(name, depth) { out[c.FullPath] = c.Entity } return out } func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { - conn, err := db.openConn() - if err != nil { - return err - } - defer conn.Close() - - for c := range db.children(conn, name, depth) { + for c := range db.children(name, depth) { if err := walkFunc(c.FullPath, c.Entity); err != nil { return err } @@ -230,14 +207,8 @@ func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { // Return the refrence count for a specified id func (db *Database) Refs(id string) int { - conn, err := db.openConn() - if err != nil { - return -1 - } - defer conn.Close() - var count int - if err := conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil { + if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil { return 0 } return count @@ -246,13 +217,8 @@ func (db *Database) Refs(id string) int { // Return all the id's path references func (db *Database) RefPaths(id string) Edges { refs := Edges{} - conn, err := db.openConn() - if err != nil { - return refs - } - defer conn.Close() - rows, err := conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id) + rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id) if err != nil { return refs } @@ -278,19 +244,14 @@ func (db *Database) Delete(name string) error { if name == "/" { return fmt.Errorf("Cannot delete root entity") } - conn, err := db.openConn() - if err != nil { - return err - } - defer conn.Close() parentPath, n := splitPath(name) - parent, err := db.get(conn, parentPath) + parent, err := db.get(parentPath) if err != nil { return err } - if _, err := conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, n+"%"); err != nil { + if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, n+"%"); err != nil { return err } return nil @@ -300,22 +261,16 @@ func (db *Database) Delete(name string) error { // Walk the graph to make sure all references to the entity // are removed and return the number of references removed func (db *Database) Purge(id string) (int, error) { - conn, err := db.openConn() - if err != nil { - return -1, err - } - defer conn.Close() - rollback := func() { - conn.Exec("ROLLBACK") + db.conn.Exec("ROLLBACK") } - if _, err := conn.Exec("BEGIN"); err != nil { + if _, err := db.conn.Exec("BEGIN"); err != nil { return -1, err } // Delete all edges - rows, err := conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id) + rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id) if err != nil { rollback() return -1, err @@ -327,12 +282,12 @@ func (db *Database) Purge(id string) (int, error) { } // Delete entity - if _, err := conn.Exec("DELETE FROM entity where id = ?;", id); err != nil { + if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil { rollback() return -1, err } - if _, err := conn.Exec("COMMIT"); err != nil { + if _, err := db.conn.Exec("COMMIT"); err != nil { return -1, err } return int(changes), nil @@ -347,18 +302,12 @@ func (db *Database) Rename(currentName, newName string) error { return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath) } - conn, err := db.openConn() - if err != nil { - return err - } - defer conn.Close() - - parent, err := db.get(conn, parentPath) + parent, err := db.get(parentPath) if err != nil { return err } - rows, err := conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name LIKE ?;", newEdgeName, parent.id, name+"%") + rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name LIKE ?;", newEdgeName, parent.id, name+"%") if err != nil { return err } @@ -379,16 +328,16 @@ type WalkMeta struct { Edge *Edge } -func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMeta { +func (db *Database) children(name string, depth int) <-chan WalkMeta { out := make(chan WalkMeta) - e, err := db.get(conn, name) + e, err := db.get(name) if err != nil { close(out) return out } go func() { - rows, err := conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) + rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id) if err != nil { close(out) } @@ -422,7 +371,7 @@ func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMe if depth != -1 { nDepth -= 1 } - sc := db.children(conn, meta.FullPath, nDepth) + sc := db.children(meta.FullPath, nDepth) for c := range sc { out <- c } @@ -433,18 +382,14 @@ func (db *Database) children(conn *sql.DB, name string, depth int) <-chan WalkMe } // Return the entity based on the parent path and name -func (db *Database) child(conn *sql.DB, parent *Entity, name string) *Entity { +func (db *Database) child(parent *Entity, name string) *Entity { var id string - if err := conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, name+"%").Scan(&id); err != nil { + if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, name+"%").Scan(&id); err != nil { return nil } return &Entity{id} } -func (db *Database) openConn() (*sql.DB, error) { - return sql.Open("sqlite3", db.dbPath) -} - // Return the id used to reference this entity func (e *Entity) ID() string { return e.id diff --git a/gograph/gograph_test.go b/gograph/gograph_test.go index 5c12128ca8..57077e9f36 100644 --- a/gograph/gograph_test.go +++ b/gograph/gograph_test.go @@ -1,35 +1,39 @@ package gograph import ( + _ "code.google.com/p/gosqlite/sqlite3" + "database/sql" "os" "path" "strconv" "testing" ) -func newTestDb(t *testing.T) *Database { - db, err := NewDatabase(path.Join(os.TempDir(), "sqlite.db")) +func newTestDb(t *testing.T) (*Database, string) { + p := path.Join(os.TempDir(), "sqlite.db") + conn, err := sql.Open("sqlite3", p) + db, err := NewDatabase(conn, true) if err != nil { t.Fatal(err) } - return db + return db, p } -func destroyTestDb(db *Database) { - os.Remove(db.dbPath) +func destroyTestDb(dbPath string) { + os.Remove(dbPath) } func TestNewDatabase(t *testing.T) { - db := newTestDb(t) + db, dbpath := newTestDb(t) if db == nil { t.Fatal("Database should not be nil") } - defer destroyTestDb(db) + defer destroyTestDb(dbpath) } func TestCreateRootEnity(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) root := db.RootEntity() if root == nil { t.Fatal("Root entity should not be nil") @@ -37,8 +41,8 @@ func TestCreateRootEnity(t *testing.T) { } func TestGetRootEntity(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) e := db.Get("/") if e == nil { @@ -50,8 +54,8 @@ func TestGetRootEntity(t *testing.T) { } func TestSetEntityWithDifferentName(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) db.Set("/test", "1") if _, err := db.Set("/other", "1"); err != nil { @@ -60,8 +64,8 @@ func TestSetEntityWithDifferentName(t *testing.T) { } func TestSetDuplicateEntity(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) if _, err := db.Set("/foo", "42"); err != nil { t.Fatal(err) @@ -72,8 +76,8 @@ func TestSetDuplicateEntity(t *testing.T) { } func TestCreateChild(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) child, err := db.Set("/db", "1") if err != nil { @@ -88,8 +92,8 @@ func TestCreateChild(t *testing.T) { } func TestListAllRootChildren(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) for i := 1; i < 6; i++ { a := strconv.Itoa(i) @@ -104,8 +108,8 @@ func TestListAllRootChildren(t *testing.T) { } func TestListAllSubChildren(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) _, err := db.Set("/webapp", "1") if err != nil { @@ -146,8 +150,8 @@ func TestListAllSubChildren(t *testing.T) { } func TestAddSelfAsChild(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) child, err := db.Set("/test", "1") if err != nil { @@ -159,8 +163,8 @@ func TestAddSelfAsChild(t *testing.T) { } func TestAddChildToNonExistantRoot(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) if _, err := db.Set("/myapp", "1"); err != nil { t.Fatal(err) @@ -172,8 +176,8 @@ func TestAddChildToNonExistantRoot(t *testing.T) { } func TestWalkAll(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) _, err := db.Set("/webapp", "1") if err != nil { t.Fatal(err) @@ -218,8 +222,8 @@ func TestWalkAll(t *testing.T) { } func TestGetEntityByPath(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) _, err := db.Set("/webapp", "1") if err != nil { t.Fatal(err) @@ -265,8 +269,8 @@ func TestGetEntityByPath(t *testing.T) { } func TestEnitiesPaths(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) _, err := db.Set("/webapp", "1") if err != nil { t.Fatal(err) @@ -309,8 +313,8 @@ func TestEnitiesPaths(t *testing.T) { } func TestDeleteRootEntity(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) if err := db.Delete("/"); err == nil { t.Fatal("Error should not be nil") @@ -318,8 +322,8 @@ func TestDeleteRootEntity(t *testing.T) { } func TestDeleteEntity(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) _, err := db.Set("/webapp", "1") if err != nil { t.Fatal(err) @@ -365,8 +369,8 @@ func TestDeleteEntity(t *testing.T) { } func TestCountRefs(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) db.Set("/webapp", "1") @@ -382,8 +386,8 @@ func TestCountRefs(t *testing.T) { } func TestPurgeId(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) db.Set("/webapp", "1") @@ -404,8 +408,8 @@ func TestPurgeId(t *testing.T) { } func TestRename(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) db.Set("/webapp", "1") @@ -433,8 +437,8 @@ func TestRename(t *testing.T) { } func TestCreateMultipleNames(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) db.Set("/db", "1") if _, err := db.Set("/myapp", "1"); err != nil { @@ -448,8 +452,8 @@ func TestCreateMultipleNames(t *testing.T) { } func TestRefPaths(t *testing.T) { - db := newTestDb(t) - defer destroyTestDb(db) + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) db.Set("/webapp", "1") @@ -460,5 +464,4 @@ func TestRefPaths(t *testing.T) { if len(refs) != 2 { t.Fatalf("Expected reference count to be 2, got %d", len(refs)) } - } diff --git a/runtime.go b/runtime.go index 0a492cd77e..5af7da7be8 100644 --- a/runtime.go +++ b/runtime.go @@ -1,7 +1,9 @@ package docker import ( + _ "code.google.com/p/gosqlite/sqlite3" "container/list" + "database/sql" "fmt" "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/utils" @@ -593,7 +595,19 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { return nil, err } - graph, err := gograph.NewDatabase(path.Join(config.GraphPath, "linkgraph.db")) + gographPath := path.Join(config.GraphPath, "linkgraph.db") + initDatabase := false + if _, err := os.Stat(gographPath); err != nil { + if os.IsNotExist(err) { + initDatabase = true + } + return nil, err + } + conn, err := sql.Open("sqlite3", gographPath) + if err != nil { + return nil, err + } + graph, err := gograph.NewDatabase(conn, initDatabase) if err != nil { return nil, err } @@ -617,6 +631,11 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { return runtime, nil } +func (runtime *Runtime) Close() error { + runtime.networkManager.Close() + return runtime.containerGraph.Close() +} + // History is a convenience type for storing a list of containers, // ordered by creation date. type History []*Container diff --git a/runtime_test.go b/runtime_test.go index 0aa2d3d543..040d06851e 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -44,7 +44,9 @@ func nuke(runtime *Runtime) error { }(container) } wg.Wait() - runtime.networkManager.Close() + runtime.Close() + + os.Remove(filepath.Join(runtime.config.GraphPath, "linkgraph.db")) return os.RemoveAll(runtime.config.GraphPath) } diff --git a/server.go b/server.go index 8e7a31ce9b..31b351fcc6 100644 --- a/server.go +++ b/server.go @@ -24,6 +24,10 @@ import ( "time" ) +func (srv *Server) Close() error { + return srv.runtime.Close() +} + func (srv *Server) DockerVersion() APIVersion { return APIVersion{ Version: VERSION, From 0c9b319dd0c29621c76cf63d494c9781e3f4ea00 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 23 Oct 2013 07:08:45 +0000 Subject: [PATCH 26/40] Hack: fix test and dyntest to correctly use $TESTFLAGS --- hack/make/dyntest | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hack/make/dyntest b/hack/make/dyntest index 848ed8b5cc..67680625e9 100644 --- a/hack/make/dyntest +++ b/hack/make/dyntest @@ -22,8 +22,9 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir + go test -i export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION - go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS + go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS $TESTFLAGS ) done } 2>&1 | tee $DEST/test.log } From 7d440f70fd1ba10efa9ab239516dca461535da95 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 23 Oct 2013 11:01:17 -0700 Subject: [PATCH 27/40] Add missing else when checking for linkgraph.db --- runtime.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index 5af7da7be8..3263d9cf7a 100644 --- a/runtime.go +++ b/runtime.go @@ -600,8 +600,9 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if _, err := os.Stat(gographPath); err != nil { if os.IsNotExist(err) { initDatabase = true + } else { + return nil, err } - return nil, err } conn, err := sql.Open("sqlite3", gographPath) if err != nil { From 7c882a800309f2a2870c57e85232ae8888bcef25 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 23 Oct 2013 15:21:28 -0700 Subject: [PATCH 28/40] Move name parsing logic to GetByName method --- gograph/gograph.go | 3 +-- runtime.go | 11 +++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/gograph/gograph.go b/gograph/gograph.go index a8af0600cc..9f477c4cf5 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -103,8 +103,7 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) { rollback := func() { db.conn.Exec("ROLLBACK") } - // FIXME: use exclusive transactions to avoid race conditions - if _, err := db.conn.Exec("BEGIN"); err != nil { + if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil { return nil, err } var entityId string diff --git a/runtime.go b/runtime.go index 3263d9cf7a..186ff4db8a 100644 --- a/runtime.go +++ b/runtime.go @@ -62,9 +62,6 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { // Get looks for a container by the specified ID or name, and returns it. // If the container is not found, or if an error occurs, nil is returned. func (runtime *Runtime) Get(name string) *Container { - if name[0] != '/' { - name = "/" + name - } if c, _ := runtime.GetByName(name); c != nil { return c } @@ -478,6 +475,10 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } func (runtime *Runtime) GetByName(name string) (*Container, error) { + if name[0] != '/' { + name = "/" + name + } + if id, err := runtime.idIndex.Get(name); err == nil { name = id } @@ -544,9 +545,7 @@ func (runtime *Runtime) Link(parentName, childName, alias string) error { if child == nil { return fmt.Errorf("Could not get container for %s", childName) } - cc := runtime.Get(child.ID()) - - _, err := runtime.containerGraph.Set(path.Join(parentName, alias), cc.ID) + _, err := runtime.containerGraph.Set(path.Join(parentName, alias), child.ID()) return err } From 66a159342bfa1b2bb8361f21a17279aa0417bb50 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 23 Oct 2013 16:41:59 -0700 Subject: [PATCH 29/40] update docker port command --- commands.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index 6251feb34b..a4b9ee0464 100644 --- a/commands.go +++ b/commands.go @@ -720,11 +720,11 @@ func (cli *DockerCli) CmdPort(args ...string) error { } port := cmd.Arg(1) - proto := "Tcp" + proto := "tcp" parts := strings.SplitN(port, "/", 2) if len(parts) == 2 && len(parts[1]) != 0 { port = parts[0] - proto = strings.ToUpper(parts[1][:1]) + strings.ToLower(parts[1][1:]) + proto = parts[1] } body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) if err != nil { @@ -736,8 +736,14 @@ func (cli *DockerCli) CmdPort(args ...string) error { return err } - if frontend, exists := out.NetworkSettings.PortMapping[proto][port]; exists { - fmt.Fprintf(cli.out, "%s\n", frontend) + if frontends, exists := out.NetworkSettings.Ports[Port(port+"/"+proto)]; exists { + if frontends == nil { + fmt.Fprintf(cli.out, "%s\n", port) + } else { + for _, frontend := range frontends { + fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) + } + } } else { return fmt.Errorf("Error: No private port '%s' allocated on %s", cmd.Arg(1), cmd.Arg(0)) } From 640682d56c9694da092e084492e3f3e96bca5c76 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 23 Oct 2013 18:27:41 -0600 Subject: [PATCH 30/40] Fix "go test -i" to include the proper LDFLAGS and BUILDFLAGS --- hack/make/dyntest | 2 +- hack/make/test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/make/dyntest b/hack/make/dyntest index 67680625e9..7de144e8db 100644 --- a/hack/make/dyntest +++ b/hack/make/dyntest @@ -22,7 +22,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - go test -i + go test -i -ldflags "$LDFLAGS" $BUILDFLAGS export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS $TESTFLAGS ) done diff --git a/hack/make/test b/hack/make/test index f905fa14a5..8acd63461d 100644 --- a/hack/make/test +++ b/hack/make/test @@ -16,7 +16,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - go test -i + go test -i -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS ) done } 2>&1 | tee $DEST/test.log From da864a845b94db64de8dcee12f62c26c57008f71 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 23 Oct 2013 17:32:40 -0700 Subject: [PATCH 31/40] Remove proto frpm default port --- links.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/links.go b/links.go index f1087ec34a..9b3d0b8886 100644 --- a/links.go +++ b/links.go @@ -54,7 +54,7 @@ func (l *Link) ToEnv() []string { alias := strings.ToUpper(l.Alias()) if p := l.getDefaultPort(); p != nil { - env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port())) + env = append(env, fmt.Sprintf("%s_PORT=%s:%s", alias, l.ChildIP, p.Port())) } // Load exposed ports into the environment From e179a24ad5f86d27377007303a2991870d173ce0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 24 Oct 2013 08:57:40 -0700 Subject: [PATCH 32/40] Update TestLinkEnv for default port --- links_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/links_test.go b/links_test.go index 64608e15c1..0865732890 100644 --- a/links_test.go +++ b/links_test.go @@ -89,8 +89,8 @@ func TestLinkEnv(t *testing.T) { } env[parts[0]] = parts[1] } - if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" { - t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT"]) + if env["DOCKER_PORT"] != "172.0.17.2:6379" { + t.Fatalf("Expected 172.0.17.2:6379, got %s", env["DOCKER_PORT"]) } if env["DOCKER_PORT_6379_TCP"] != "tcp://172.0.17.2:6379" { t.Fatalf("Expected tcp://172.0.17.2:6379, got %s", env["DOCKER_PORT_6379_TCP"]) From 94e5081baca9a3b3408d3a6ffa2926d1e262d6fe Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 24 Oct 2013 09:08:50 -0700 Subject: [PATCH 33/40] Move iptable rules outside of create bridge This allows the user to toggle enabling and disabling intercontainer communication when they run the daemon. --- iptables/iptables.go | 5 +++++ network.go | 27 +++++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/iptables/iptables.go b/iptables/iptables.go index 5974d4d9c6..463f443fda 100644 --- a/iptables/iptables.go +++ b/iptables/iptables.go @@ -92,6 +92,11 @@ func (c *Chain) Remove() error { return nil } +// Check if an existing rule exists +func Exists(args ...string) bool { + return Raw(append([]string{"-C"}, args...)...) == nil +} + func Raw(args ...string) error { path, err := exec.LookPath("iptables") if err != nil { diff --git a/network.go b/network.go index 593ed6cab0..c237ccc86d 100644 --- a/network.go +++ b/network.go @@ -145,16 +145,6 @@ func CreateBridgeIface(config *DaemonConfig) error { "!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil { return fmt.Errorf("Unable to enable network bridge NAT: %s", err) } - - if !config.InterContainerCommunication { - utils.Debugf("Disable inter-container communication") - if err := iptables.Raw("-A", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"); err != nil { - return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) - } - } else { - utils.Debugf("Enable inter-container communication") - iptables.Raw("-D", "FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP") - } } return nil } @@ -659,6 +649,23 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } network := addr.(*net.IPNet) + // Configure iptables for link support + if config.EnableIptables { + args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"} + + if !config.InterContainerCommunication { + if !iptables.Exists(args...) { + utils.Debugf("Disable inter-container communication") + if err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil { + return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err) + } + } + } else { + utils.Debugf("Enable inter-container communication") + iptables.Raw(append([]string{"-D"}, args...)...) + } + } + ipAllocator := newIPAllocator(network) tcpPortAllocator, err := newPortAllocator() From 2e6b241dc70c0f1ed6745af1f27b11bf77c84f3b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 24 Oct 2013 10:25:07 -0700 Subject: [PATCH 34/40] Add default entity and edge to graph on runtime load --- runtime.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/runtime.go b/runtime.go index 186ff4db8a..9ac9269317 100644 --- a/runtime.go +++ b/runtime.go @@ -257,6 +257,13 @@ func (runtime *Runtime) restore() error { return len(ic.Links) < len(jc.Links) }) for _, container := range containers { + + // Try to set the default name for a container if it exists prior to links + // Ignore the error because if it already exists you will get an invalid constraint + if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", container.ID), container.ID); err != nil { + utils.Debugf("Setting default id - %s", err) + } + if err := runtime.Register(container); err != nil { utils.Debugf("Failed to register container %s: %s", container.ID, err) continue From 356af1540f9a43337571e0f3ad6038789b38478e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 24 Oct 2013 16:49:28 -0700 Subject: [PATCH 35/40] Ensure child containers are started before parents --- gograph/gograph.go | 2 +- gograph/sort.go | 2 +- gograph/utils.go | 2 +- runtime.go | 67 ++++++++++++++++++++++++++++------------------ runtime_test.go | 4 +-- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/gograph/gograph.go b/gograph/gograph.go index 9f477c4cf5..48693d3ec3 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -177,7 +177,7 @@ func (db *Database) get(name string) (*Entity, error) { next := db.child(e, p) if next == nil { - return nil, fmt.Errorf("Cannot find child") + return nil, fmt.Errorf("Cannot find child for %s", name) } e = next } diff --git a/gograph/sort.go b/gograph/sort.go index cc936cb840..a0af6b4025 100644 --- a/gograph/sort.go +++ b/gograph/sort.go @@ -9,7 +9,7 @@ type pathSorter struct { func sortByDepth(paths []string) { s := &pathSorter{paths, func(i, j string) bool { - return pathDepth(i) > pathDepth(j) + return PathDepth(i) > PathDepth(j) }} sort.Sort(s) } diff --git a/gograph/utils.go b/gograph/utils.go index c20dd124a2..4896242796 100644 --- a/gograph/utils.go +++ b/gograph/utils.go @@ -11,7 +11,7 @@ func split(p string) []string { } // Returns the depth or number of / in a given path -func pathDepth(p string) int { +func PathDepth(p string) int { parts := split(p) if len(parts) == 2 && parts[1] == "" { return 1 diff --git a/runtime.go b/runtime.go index 9ac9269317..58c2007f7b 100644 --- a/runtime.go +++ b/runtime.go @@ -230,7 +230,8 @@ func (runtime *Runtime) restore() error { if err != nil { return err } - containers := []*Container{} + containers := make(map[string]*Container) + for i, v := range dir { id := v.Name() container, err := runtime.load(id) @@ -242,33 +243,34 @@ func (runtime *Runtime) restore() error { continue } utils.Debugf("Loaded container %v", container.ID) - containers = append(containers, container) + containers[container.ID] = container } - sortContainers(containers, func(i, j *Container) bool { - ic, _ := i.ReadHostConfig() - jc, _ := j.ReadHostConfig() - if ic == nil || ic.Links == nil { - return true + register := func(container *Container) { + if err := runtime.Register(container); err != nil { + utils.Debugf("Failed to register container %s: %s", container.ID, err) } - if jc == nil || jc.Links == nil { - return false + } + + if entities := runtime.containerGraph.List("/", -1); entities != nil { + for _, p := range entities.Paths() { + e := entities[p] + if container, ok := containers[e.ID()]; ok { + register(container) + delete(containers, e.ID()) + } } - return len(ic.Links) < len(jc.Links) - }) + } + + // Any containers that are left over do not exist in the graph for _, container := range containers { - // Try to set the default name for a container if it exists prior to links - // Ignore the error because if it already exists you will get an invalid constraint if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", container.ID), container.ID); err != nil { utils.Debugf("Setting default id - %s", err) } - - if err := runtime.Register(container); err != nil { - utils.Debugf("Failed to register container %s: %s", container.ID, err) - continue - } + register(container) } + if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf("\bdone.\n") } @@ -481,14 +483,24 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a return img, nil } -func (runtime *Runtime) GetByName(name string) (*Container, error) { - if name[0] != '/' { - name = "/" + name +// Strip the leading slash from the name to look up if it +// is a truncated id +// Prepend the slash back after finding the name +func (runtime *Runtime) getFullName(name string) string { + if name[0] == '/' { + name = name[1:] } - if id, err := runtime.idIndex.Get(name); err == nil { name = id } + if name[0] != '/' { + name = "/" + name + } + return name +} + +func (runtime *Runtime) GetByName(name string) (*Container, error) { + name = runtime.getFullName(name) entity := runtime.containerGraph.Get(name) if entity == nil { @@ -520,17 +532,20 @@ func (runtime *Runtime) Children(name string) (map[string]*Container, error) { } func (runtime *Runtime) RenameLink(oldName, newName string) error { - if id, err := runtime.idIndex.Get(oldName); err == nil { - oldName = id - } + oldName = runtime.getFullName(oldName) + entity := runtime.containerGraph.Get(oldName) if entity == nil { return fmt.Errorf("Could not find entity for %s", oldName) } + if newName[0] != '/' { + newName = "/" + newName + } + // This is not rename but adding a new link for the default name // Strip the leading '/' - if strings.HasPrefix(entity.ID(), oldName[1:]) { + if entity.ID() == oldName[1:] { _, err := runtime.containerGraph.Set(newName, entity.ID()) return err } diff --git a/runtime_test.go b/runtime_test.go index 040d06851e..085e5be1a3 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -572,7 +572,7 @@ func TestReloadContainerLinks(t *testing.T) { } h1 := &HostConfig{} // Add a link to container 2 - h1.Links = []string{utils.TruncateID(container2.ID) + ":first"} + h1.Links = []string{"/" + container2.ID + ":first"} if err := container1.Start(h1); err != nil { t.Fatal(err) } @@ -618,7 +618,7 @@ func TestReloadContainerLinks(t *testing.T) { t.Fatalf("Container 2 %s should be registered first in the runtime", container2.ID) } - t.Logf("Number of links: %d", runtime2.containerGraph.Refs("engine")) + t.Logf("Number of links: %d", runtime2.containerGraph.Refs("0")) // Verify that the link is still registered in the runtime entity := runtime2.containerGraph.Get(fmt.Sprintf("/%s", container1.ID)) if entity == nil { From 516cf548433ca17098533110fffc1998c7114e2e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 25 Oct 2013 16:49:49 -0700 Subject: [PATCH 36/40] prevent default name to be deleted and return an error on invalid link --- gograph/gograph.go | 6 +++--- server.go | 35 ++++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/gograph/gograph.go b/gograph/gograph.go index 48693d3ec3..caec3bffa2 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -250,7 +250,7 @@ func (db *Database) Delete(name string) error { return err } - if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, n+"%"); err != nil { + if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil { return err } return nil @@ -306,7 +306,7 @@ func (db *Database) Rename(currentName, newName string) error { return err } - rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name LIKE ?;", newEdgeName, parent.id, name+"%") + rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name) if err != nil { return err } @@ -383,7 +383,7 @@ func (db *Database) children(name string, depth int) <-chan WalkMeta { // Return the entity based on the parent path and name func (db *Database) child(parent *Entity, name string) *Entity { var id string - if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name LIKE ?;", parent.id, name+"%").Scan(&id); err != nil { + if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil { return nil } return &Entity{id} diff --git a/server.go b/server.go index 31b351fcc6..d1bbe1bfe3 100644 --- a/server.go +++ b/server.go @@ -979,34 +979,39 @@ func (srv *Server) ContainerRestart(name string, t int) error { } func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) error { + container := srv.runtime.Get(name) + if removeLink { - p := name - if p[0] != '/' { - p = "/" + p - } - parent, n := path.Split(p) - l := len(parent) - if parent[l-1] == '/' { - parent = parent[:l-1] + if container == nil { + return fmt.Errorf("No such link: %s", name) } + name = srv.runtime.getFullName(name) + + parent, n := path.Split(name) pe := srv.runtime.containerGraph.Get(parent) - parentContainer := srv.runtime.Get(pe.ID()) + if pe != nil { + parentContainer := srv.runtime.Get(pe.ID()) - if parentContainer != nil && parentContainer.activeLinks != nil { - if link, exists := parentContainer.activeLinks[n]; exists { - link.Disable() - } else { - utils.Debugf("Could not find active link for %s", name) + if parentContainer != nil && parentContainer.activeLinks != nil { + if link, exists := parentContainer.activeLinks[n]; exists { + link.Disable() + } else { + utils.Debugf("Could not find active link for %s", name) + } } } + if name[1:] == container.ID { + return fmt.Errorf("Conflict, cannot remove default link %s", name) + } + if err := srv.runtime.containerGraph.Delete(name); err != nil { return err } return nil } - if container := srv.runtime.Get(name); container != nil { + if container != nil { if container.State.Running { return fmt.Errorf("Impossible to remove a running container, please stop it first") } From d5c7e92a9a8ba65e1b2aad0f21393e8986d03d55 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 25 Oct 2013 17:36:23 -0700 Subject: [PATCH 37/40] fix docker ps display --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index a4b9ee0464..ef265429ab 100644 --- a/commands.go +++ b/commands.go @@ -1170,7 +1170,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { out.Names[i] = utils.Trunc(out.Names[i], 10) } } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ",")) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ",")) if *size { if out.SizeRootFs > 0 { fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs)) From 971cf56d89ac9cc4959800bdfaadb69ba2cdd06a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 28 Oct 2013 14:23:33 -0700 Subject: [PATCH 38/40] add name generator --- names-generator/names-generator.go | 30 +++++++++++++++++++++++++ names-generator/names-generator_test.go | 28 +++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 names-generator/names-generator.go create mode 100644 names-generator/names-generator_test.go diff --git a/names-generator/names-generator.go b/names-generator/names-generator.go new file mode 100644 index 0000000000..afbaf74cff --- /dev/null +++ b/names-generator/names-generator.go @@ -0,0 +1,30 @@ +package namesgenerator + +import ( + "fmt" + "math/rand" + "time" +) + +type NameChecker interface { + Exists(name string) bool +} + +var ( + colors = [...]string{"white", "silver", "gray", "black", "blue", "green", "cyan", "yellow", "gold", "orange", "brown", "red", "violet", "pink", "magenta", "purple"} + animals = [...]string{"ant", "bird", "cat", "chicken", "cow", "dog", "fish", "fox", "horse", "lion", "monkey", "pig", "sheep", "tiger", "whale", "wolf"} +) + +func GenerateRandomName(checker NameChecker) (string, error) { + retry := 5 + rand.Seed(time.Now().UnixNano()) + name := fmt.Sprintf("%s_%s", colors[rand.Intn(len(colors))], animals[rand.Intn(len(animals))]) + for checker != nil && checker.Exists(name) && retry > 0 { + name = fmt.Sprintf("%s%d", name, rand.Intn(10)) + retry = retry - 1 + } + if retry == 0 { + return name, fmt.Errorf("Error generating random name") + } + return name, nil +} diff --git a/names-generator/names-generator_test.go b/names-generator/names-generator_test.go new file mode 100644 index 0000000000..7ca225ac7a --- /dev/null +++ b/names-generator/names-generator_test.go @@ -0,0 +1,28 @@ +package namesgenerator + +import ( + "testing" +) + +type FalseChecker struct{} + +func (n *FalseChecker) Exists(name string) bool { + return false +} + +type TrueChecker struct{} + +func (n *TrueChecker) Exists(name string) bool { + return true +} + +func TestGenerateRandomName(t *testing.T) { + if _, err := GenerateRandomName(&FalseChecker{}); err != nil { + t.Error(err) + } + + if _, err := GenerateRandomName(&TrueChecker{}); err == nil { + t.Error("An error was expected") + } + +} From 0d2924408bad7aa797bfaf0610e836d82928296f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 28 Oct 2013 16:58:59 -0700 Subject: [PATCH 39/40] Add -name for docker run Remove docker link Do not add container id as default name Create an auto generated container name if not specified at runtime. --- api.go | 80 +++---------------- api_params.go | 6 -- api_test.go | 17 +++- buildfile.go | 6 +- commands.go | 41 ++++------ container.go | 6 +- container_test.go | 41 ++++++++-- docs/sources/api/docker_remote_api_v1.6.rst | 1 + docs/sources/commandline/cli.rst | 42 +++------- docs/sources/examples/linking_into_redis.rst | 34 +++----- gograph/gograph.go | 24 ++++-- gograph/gograph_test.go | 24 ++++++ links.go | 2 +- links_test.go | 2 +- .../names-generator.go | 0 .../names-generator_test.go | 0 runtime.go | 76 ++++++------------ runtime_test.go | 77 +++++++++--------- server.go | 31 ++++--- server_test.go | 13 +-- sorter.go | 22 ----- utils.go | 18 +++++ utils_test.go | 2 +- 23 files changed, 258 insertions(+), 307 deletions(-) rename {names-generator => namesgenerator}/names-generator.go (100%) rename {names-generator => namesgenerator}/names-generator_test.go (100%) diff --git a/api.go b/api.go index 178ef4d353..638e78a713 100644 --- a/api.go +++ b/api.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "github.com/dotcloud/docker/auth" - "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/utils" "github.com/gorilla/mux" "io" @@ -522,8 +521,12 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http } func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return nil + } config := &Config{} out := &APIRun{} + name := r.Form.Get("name") if err := json.NewDecoder(r.Body).Decode(config); err != nil { return err @@ -539,7 +542,7 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r config.Dns = defaultDns } - id, warnings, err := srv.ContainerCreate(config) + id, warnings, err := srv.ContainerCreate(config, name) if err != nil { return err } @@ -649,6 +652,10 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } name := vars["name"] + // Register any links from the host config before starting the container + if err := srv.RegisterLinks(name, hostConfig); err != nil { + return err + } if err := srv.ContainerStart(name, hostConfig); err != nil { return err } @@ -1019,73 +1026,6 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s } } -func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { - return err - } - - runtime := srv.runtime - all, err := getBoolParam(r.Form.Get("all")) - if err != nil { - return err - } - - out := []APILink{} - err = runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { - if container := runtime.Get(e.ID()); container != nil { - if !all && strings.Contains(p, container.ID) { - return nil - } - out = append(out, APILink{ - Path: p, - ContainerID: container.ID, - Image: runtime.repositories.ImageName(container.Image), - }) - } - return nil - }, -1) - - if err != nil { - return err - } - return writeJSON(w, http.StatusOK, out) -} - -func postContainerLink(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if vars == nil { - return fmt.Errorf("Missing parameter") - } - values := make(map[string]string) - if matchesContentType(r.Header.Get("Content-Type"), "application/json") && r.Body != nil { - defer r.Body.Close() - - dec := json.NewDecoder(r.Body) - if err := dec.Decode(&values); err != nil { - return err - } - } else { - return fmt.Errorf("Invalid json body") - } - currentName := values["currentName"] - newName := values["newName"] - - if currentName == "" { - return fmt.Errorf("currentName cannot be empty") - } - if newName == "" { - return fmt.Errorf("newName cannot be empty") - } - - if err := srv.runtime.RenameLink(currentName, newName); err != nil { - if strings.HasSuffix(err.Error(), "name are not unique") { - return fmt.Errorf("Conflict, %s already exists", newName) - } - return err - } - - return nil -} - func createRouter(srv *Server, logging bool) (*mux.Router, error) { r := mux.NewRouter() @@ -1106,7 +1046,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/containers/{name:.*}/json": getContainersByName, "/containers/{name:.*}/top": getContainersTop, "/containers/{name:.*}/attach/ws": wsContainersAttach, - "/containers/links": getContainersLinks, }, "POST": { "/auth": postAuth, @@ -1125,7 +1064,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/containers/{name:.*}/resize": postContainersResize, "/containers/{name:.*}/attach": postContainersAttach, "/containers/{name:.*}/copy": postContainersCopy, - "/containers/link": postContainerLink, }, "DELETE": { "/containers/{name:.*}": deleteContainers, diff --git a/api_params.go b/api_params.go index 5242d02221..40738f9fe9 100644 --- a/api_params.go +++ b/api_params.go @@ -121,9 +121,3 @@ type APICopy struct { Resource string HostPath string } - -type APILink struct { - Path string - ContainerID string - Image string -} diff --git a/api_test.go b/api_test.go index 94a19a1362..34c89f8294 100644 --- a/api_test.go +++ b/api_test.go @@ -352,7 +352,7 @@ func TestGetContainersJSON(t *testing.T) { container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, - }) + }, "") if err != nil { t.Fatal(err) } @@ -391,6 +391,7 @@ func TestGetContainersExport(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }, + "", ) if err != nil { t.Fatal(err) @@ -441,6 +442,7 @@ func TestGetContainersChanges(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/rm", "/etc/passwd"}, }, + "", ) if err != nil { t.Fatal(err) @@ -485,6 +487,7 @@ func TestGetContainersTop(t *testing.T) { Cmd: []string{"/bin/sh", "-c", "cat"}, OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -566,6 +569,7 @@ func TestGetContainersByName(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, }, + "", ) if err != nil { t.Fatal(err) @@ -597,6 +601,7 @@ func TestPostCommit(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }, + "", ) if err != nil { t.Fatal(err) @@ -692,6 +697,7 @@ func TestPostContainersKill(t *testing.T) { Cmd: []string{"/bin/cat"}, OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -734,6 +740,7 @@ func TestPostContainersRestart(t *testing.T) { Cmd: []string{"/bin/top"}, OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -788,6 +795,7 @@ func TestPostContainersStart(t *testing.T) { Cmd: []string{"/bin/cat"}, OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -840,6 +848,7 @@ func TestPostContainersStop(t *testing.T) { Cmd: []string{"/bin/top"}, OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -887,6 +896,7 @@ func TestPostContainersWait(t *testing.T) { Cmd: []string{"/bin/sleep", "1"}, OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -929,6 +939,7 @@ func TestPostContainersAttach(t *testing.T) { Cmd: []string{"/bin/cat"}, OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -1018,6 +1029,7 @@ func TestPostContainersAttachStderr(t *testing.T) { Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -1107,7 +1119,7 @@ func TestDeleteContainers(t *testing.T) { container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, - }) + }, "") if err != nil { t.Fatal(err) } @@ -1299,6 +1311,7 @@ func TestPostContainersCopy(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test.txt"}, }, + "", ) if err != nil { t.Fatal(err) diff --git a/buildfile.go b/buildfile.go index a41443a965..effad01ef0 100644 --- a/buildfile.go +++ b/buildfile.go @@ -335,7 +335,7 @@ func (b *buildFile) CmdAdd(args string) error { b.config.Image = b.image // Create the container and start it - container, _, err := b.runtime.Create(b.config) + container, _, err := b.runtime.Create(b.config, "") if err != nil { return err } @@ -370,7 +370,7 @@ func (b *buildFile) run() (string, error) { b.config.Image = b.image // Create the container and start it - c, _, err := b.runtime.Create(b.config) + c, _, err := b.runtime.Create(b.config, "") if err != nil { return "", err } @@ -433,7 +433,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { } } - container, _, err := b.runtime.Create(b.config) + container, _, err := b.runtime.Create(b.config, "") if err != nil { return err } diff --git a/commands.go b/commands.go index ef265429ab..31d201f6cb 100644 --- a/commands.go +++ b/commands.go @@ -91,7 +91,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"insert", "Insert a file in an image"}, {"inspect", "Return low-level information on a container"}, {"kill", "Kill a running container"}, - {"link", "Link the container with a new name"}, {"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"}, @@ -1163,12 +1162,15 @@ func (cli *DockerCli) CmdPs(args ...string) error { if !*noTrunc { out.ID = utils.TruncateID(out.ID) } + + // Remove the leading / from the names + for i := 0; i < len(out.Names); i++ { + out.Names[i] = out.Names[i][1:] + } + if !*quiet { if !*noTrunc { out.Command = utils.Trunc(out.Command, 20) - for i := 0; i < len(out.Names); i++ { - out.Names[i] = utils.Trunc(out.Names[i], 10) - } } fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ",")) if *size { @@ -1191,27 +1193,6 @@ func (cli *DockerCli) CmdPs(args ...string) error { return nil } -func (cli *DockerCli) CmdLink(args ...string) error { - cmd := Subcmd("link", "CURRENT_NAME NEW_NAME", "Link the container with a new name") - if err := cmd.Parse(args); err != nil { - return nil - } - if cmd.NArg() != 2 { - cmd.Usage() - return nil - } - body := map[string]string{ - "currentName": cmd.Arg(0), - "newName": cmd.Arg(1), - } - - _, _, err := cli.call("POST", "/containers/link", body) - if err != nil { - return err - } - return nil -} - func (cli *DockerCli) CmdCommit(args ...string) error { cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") @@ -1535,6 +1516,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { flSigProxy := cmd.Lookup("sig-proxy") sigProxy, _ := strconv.ParseBool(flSigProxy.Value.String()) + flName := cmd.Lookup("name") var containerIDFile *os.File if len(hostConfig.ContainerIDFile) > 0 { @@ -1547,9 +1529,14 @@ func (cli *DockerCli) CmdRun(args ...string) error { } defer containerIDFile.Close() } + containerValues := url.Values{} + name := flName.Value.String() + if name != "" { + containerValues.Set("name", name) + } //create the container - body, statusCode, err := cli.call("POST", "/containers/create", config) + body, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config) //if image not found try to pull it if statusCode == 404 { _, tag := utils.ParseRepositoryTag(config.Image) @@ -1590,7 +1577,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if err != nil { return err } - body, _, err = cli.call("POST", "/containers/create", config) + body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config) if err != nil { return err } diff --git a/container.go b/container.go index 9ee91a4c50..ce0ce479a3 100644 --- a/container.go +++ b/container.go @@ -44,6 +44,7 @@ type Container struct { ResolvConfPath string HostnamePath string HostsPath string + Name string cmd *exec.Cmd stdout *utils.WriteBroadcaster @@ -167,6 +168,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container") flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)") flSigProxy := cmd.Bool("sig-proxy", false, "Proxify all received signal to the process (even in non-tty mode)") + cmd.String("name", "", "Assign a name to the container") if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") @@ -199,7 +201,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") var flLinks utils.ListOpts - cmd.Var(&flLinks, "link", "Add link to another container (containerid:alias)") + cmd.Var(&flLinks, "link", "Add link to another container (name:alias)") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err @@ -858,7 +860,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) { // Init any links between the parent and children runtime := container.runtime - children, err := runtime.Children(fmt.Sprintf("/%s", container.ID)) + children, err := runtime.Children(container.Name) if err != nil { return err } diff --git a/container_test.go b/container_test.go index b8f1f0c8e7..262d6fcb1d 100644 --- a/container_test.go +++ b/container_test.go @@ -23,6 +23,7 @@ func TestIDFormat(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "echo hello world"}, }, + "", ) if err != nil { t.Fatal(err) @@ -393,6 +394,7 @@ func TestOutput(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, }, + "", ) if err != nil { t.Fatal(err) @@ -415,6 +417,7 @@ func TestContainerNetwork(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, }, + "", ) if err != nil { t.Fatal(err) @@ -438,6 +441,7 @@ func TestKillDifferentUser(t *testing.T) { OpenStdin: true, User: "daemon", }, + "", ) if err != nil { t.Fatal(err) @@ -492,7 +496,7 @@ func TestCreateVolume(t *testing.T) { if err != nil { t.Fatal(err) } - c, _, err := runtime.Create(config) + c, _, err := runtime.Create(config, "") if err != nil { t.Fatal(err) } @@ -511,6 +515,7 @@ func TestKill(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, + "", ) if err != nil { t.Fatal(err) @@ -554,7 +559,7 @@ func TestExitCode(t *testing.T) { trueContainer, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true", ""}, - }) + }, "") if err != nil { t.Fatal(err) } @@ -569,7 +574,7 @@ func TestExitCode(t *testing.T) { falseContainer, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/false", ""}, - }) + }, "") if err != nil { t.Fatal(err) } @@ -589,6 +594,7 @@ func TestRestart(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, }, + "", ) if err != nil { t.Fatal(err) @@ -621,6 +627,7 @@ func TestRestartStdin(t *testing.T) { OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -697,6 +704,7 @@ func TestUser(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, }, + "", ) if err != nil { t.Fatal(err) @@ -717,6 +725,7 @@ func TestUser(t *testing.T) { User: "root", }, + "", ) if err != nil { t.Fatal(err) @@ -737,6 +746,7 @@ func TestUser(t *testing.T) { User: "0", }, + "", ) if err != nil || container.State.ExitCode != 0 { t.Fatal(err) @@ -757,6 +767,7 @@ func TestUser(t *testing.T) { User: "1", }, + "", ) if err != nil { t.Fatal(err) @@ -779,6 +790,7 @@ func TestUser(t *testing.T) { User: "daemon", }, + "", ) if err != nil { t.Fatal(err) @@ -799,6 +811,7 @@ func TestUser(t *testing.T) { User: "unknownuser", }, + "", ) if err != nil { t.Fatal(err) @@ -818,6 +831,7 @@ func TestMultipleContainers(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, + "", ) if err != nil { t.Fatal(err) @@ -828,6 +842,7 @@ func TestMultipleContainers(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, + "", ) if err != nil { t.Fatal(err) @@ -874,6 +889,7 @@ func TestStdin(t *testing.T) { OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -919,6 +935,7 @@ func TestTty(t *testing.T) { OpenStdin: true, }, + "", ) if err != nil { t.Fatal(err) @@ -962,6 +979,7 @@ func TestEnv(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"env"}, }, + "", ) if err != nil { t.Fatal(err) @@ -1013,6 +1031,7 @@ func TestEntrypoint(t *testing.T) { Entrypoint: []string{"/bin/echo"}, Cmd: []string{"-n", "foobar"}, }, + "", ) if err != nil { t.Fatal(err) @@ -1035,6 +1054,7 @@ func TestEntrypointNoCmd(t *testing.T) { Image: GetTestImage(runtime).ID, Entrypoint: []string{"/bin/echo", "foobar"}, }, + "", ) if err != nil { t.Fatal(err) @@ -1089,6 +1109,7 @@ func TestLXCConfig(t *testing.T) { Memory: int64(mem), CpuShares: int64(cpu), }, + "", ) if err != nil { t.Fatal(err) @@ -1111,6 +1132,7 @@ func TestCustomLxcConfig(t *testing.T) { Hostname: "foobar", }, + "", ) if err != nil { t.Fatal(err) @@ -1140,6 +1162,7 @@ func BenchmarkRunSequencial(b *testing.B) { Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, + "", ) if err != nil { b.Fatal(err) @@ -1172,6 +1195,7 @@ func BenchmarkRunParallel(b *testing.B) { Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, + "", ) if err != nil { complete <- err @@ -1324,6 +1348,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) { Cmd: []string{"/bin/echo", "-n", "foobar"}, Volumes: map[string]struct{}{"/test": {}}, }, + "", ) if err != nil { t.Fatal(err) @@ -1343,6 +1368,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) { Cmd: []string{"/bin/echo", "-n", "foobar"}, VolumesFrom: container.ID, }, + "", ) if err != nil { t.Fatal(err) @@ -1378,6 +1404,7 @@ func TestRestartWithVolumes(t *testing.T) { Cmd: []string{"echo", "-n", "foobar"}, Volumes: map[string]struct{}{"/test": {}}, }, + "", ) if err != nil { t.Fatal(err) @@ -1421,6 +1448,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Volumes: map[string]struct{}{"/test": {}}, }, + "", ) if err != nil { t.Fatal(err) @@ -1450,6 +1478,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { VolumesFrom: container.ID, Volumes: map[string]struct{}{"/test": {}}, }, + "", ) if err != nil { t.Fatal(err) @@ -1484,7 +1513,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { if err != nil { t.Fatal(err) } - c, _, err := runtime.Create(config) + c, _, err := runtime.Create(config, "") if err != nil { t.Fatal(err) } @@ -1555,6 +1584,7 @@ func TestMultipleVolumesFrom(t *testing.T) { Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Volumes: map[string]struct{}{"/test": {}}, }, + "", ) if err != nil { t.Fatal(err) @@ -1583,6 +1613,7 @@ func TestMultipleVolumesFrom(t *testing.T) { Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"}, Volumes: map[string]struct{}{"/other": {}}, }, + "", ) if err != nil { t.Fatal(err) @@ -1603,7 +1634,7 @@ func TestMultipleVolumesFrom(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, VolumesFrom: strings.Join([]string{container.ID, container2.ID}, ","), - }) + }, "") if err != nil { t.Fatal(err) diff --git a/docs/sources/api/docker_remote_api_v1.6.rst b/docs/sources/api/docker_remote_api_v1.6.rst index 4a743a75ac..4d844c7a9a 100644 --- a/docs/sources/api/docker_remote_api_v1.6.rst +++ b/docs/sources/api/docker_remote_api_v1.6.rst @@ -151,6 +151,7 @@ Create a container } :jsonparam config: the container's configuration + :query name: container name to use :statuscode 201: no error :statuscode 404: no such container :statuscode 406: impossible to attach (container not running) diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index 4ced41894a..0badcd67c1 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -403,33 +403,6 @@ Insert file from github Kill a running container -.. _cli_link: - -``link`` --------- - -:: - - Usage: docker link CURRENT_NAME NEW_NAME - - Link a container to a new name. - - -Examples: -~~~~~~~~~ - -.. code-block:: bash - - $ docker link /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc /redis - $ docker ls - NAME ID IMAGE - /redis 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest - /59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc 59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc crosbymichael/redis:latest - - -This will create a new link for the existing name ``/59669e088202c2ebe150b4346cb3301562d073b51261176a354a74e8f618bfbc`` -with the new name ``/redis`` so that we can new reference the same container under the new name ``/redis``. - .. _cli_login: ``login`` @@ -604,7 +577,8 @@ network communication. -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -sig-proxy=false: Proxify all received signal to the process (even in non-tty mode) -expose=[]: Expose a port from the container without publishing it to your host - -link="": Add link to another container (containerid:alias) + -link="": Add link to another container (name:alias) + -name="": Assign the specified name to the container. If no name is specific docker will generate a random name Examples -------- @@ -680,12 +654,20 @@ without publishing the port to the host system's interfaces. .. code-block:: bash - docker run -link /redis:redis ubuntu bash + docker run -name console -t -i ubuntu bash + +This will create and run a new container with the container name +being ``console``. + +.. code-block:: bash + + docker run -link /redis:redis -name console ubuntu bash The ``-link`` flag will link the container named ``/redis`` into the newly created container with the alias ``redis``. The new container can access the network and environment of the redis container via -environment variables. +environment variables. The ``-name`` flag will assign the name ``console`` +to the newly created container. .. _cli_search: diff --git a/docs/sources/examples/linking_into_redis.rst b/docs/sources/examples/linking_into_redis.rst index 70a83b4333..4e0e612ba4 100644 --- a/docs/sources/examples/linking_into_redis.rst +++ b/docs/sources/examples/linking_into_redis.rst @@ -54,26 +54,14 @@ Run the redis container .. code-block:: bash - docker run -d -e PASSWORD=docker crosbymichael/redis --requirepass docker + docker run -d -e PASSWORD=docker -name redis crosbymichael/redis --requirepass=docker -This will run our redis container using the default port of 6379 and using -as password to secure our service. Next we will link the redis container to -a new name using ``docker link``. - - -Linking an existing container ------------------------------ - -Docker will automatically create an initial link with the container's id but -because the is long and not very user friendly we can link the container with -a new name. - -.. code-block:: bash - - docker link /39588b6a45100ef5b328b2c302ea085624f29e6cbab70f88be04793af02cec89 /redis - -Now we can reference our running redis service using the friendly name ``/redis``. -We can issue all the commands that you would expect; start, stop, attach, using the new name. +This will run our redis container using the default port of 6379 and using docker +as password to secure our service. By specifying the ``-name`` flag on run +we will assign the name ``redis`` to this container. +We can issue all the commands that you would expect; start, stop, attach, using the name. +The name also allows us to link other containers into this one. If you do not specify a +name on docker run, docker will automatically generate a name for your container. Linking redis as a child ------------------------ @@ -90,12 +78,12 @@ Now lets start our web application with a link into redis. .. code-block:: bash - docker run -t -i -link /redis:db ubuntu bash + docker run -t -i -link /redis:db -name webapp ubuntu bash root@4c01db0b339c:/# env HOSTNAME=4c01db0b339c - DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db + DB_NAME=/webapp/db TERM=xterm DB_PORT=tcp://172.17.0.8:6379 DB_PORT_6379_TCP=tcp://172.17.0.8:6379 @@ -118,7 +106,7 @@ network and environment information from the child. .. code-block:: bash # The name of the child container - DB_NAME=/4c01db0b339cf19958731255a796ee072040a652f51652a4ade190ab8c27006f/db + DB_NAME=/webapp/db # The default protocol, ip, and port of the service running in the container DB_PORT=tcp://172.17.0.8:6379 # A specific protocol, ip, and port of various services @@ -129,4 +117,4 @@ network and environment information from the child. Accessing the network information along with the environment of the child container allows us to easily connect to the redis service on the specific ip and port and use the password -specified in the environment. +specified in the environment. diff --git a/gograph/gograph.go b/gograph/gograph.go index caec3bffa2..ff0b854064 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -132,6 +132,11 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) { return e, nil } +// Return true if a name already exists in the database +func (db *Database) Exists(name string) bool { + return db.Get(name) != nil +} + func (db *Database) setEdge(parentPath, name string, e *Entity) error { parent, err := db.get(parentPath) if err != nil { @@ -189,14 +194,22 @@ func (db *Database) get(name string) (*Entity, error) { // The key will be the full path of the entity func (db *Database) List(name string, depth int) Entities { out := Entities{} - for c := range db.children(name, depth) { + e, err := db.get(name) + if err != nil { + return out + } + for c := range db.children(e, name, depth) { out[c.FullPath] = c.Entity } return out } func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { - for c := range db.children(name, depth) { + e, err := db.get(name) + if err != nil { + return err + } + for c := range db.children(e, name, depth) { if err := walkFunc(c.FullPath, c.Entity); err != nil { return err } @@ -327,10 +340,9 @@ type WalkMeta struct { Edge *Edge } -func (db *Database) children(name string, depth int) <-chan WalkMeta { +func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta { out := make(chan WalkMeta) - e, err := db.get(name) - if err != nil { + if e == nil { close(out) return out } @@ -370,7 +382,7 @@ func (db *Database) children(name string, depth int) <-chan WalkMeta { if depth != -1 { nDepth -= 1 } - sc := db.children(meta.FullPath, nDepth) + sc := db.children(child, meta.FullPath, nDepth) for c := range sc { out <- c } diff --git a/gograph/gograph_test.go b/gograph/gograph_test.go index 57077e9f36..7107ef55ea 100644 --- a/gograph/gograph_test.go +++ b/gograph/gograph_test.go @@ -28,6 +28,7 @@ func TestNewDatabase(t *testing.T) { if db == nil { t.Fatal("Database should not be nil") } + db.Close() defer destroyTestDb(dbpath) } @@ -465,3 +466,26 @@ func TestRefPaths(t *testing.T) { t.Fatalf("Expected reference count to be 2, got %d", len(refs)) } } + +func TestExistsTrue(t *testing.T) { + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) + + db.Set("/testing", "1") + + if !db.Exists("/testing") { + t.Fatalf("/tesing should exist") + } +} + +func TestExistsFalse(t *testing.T) { + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) + + db.Set("toerhe", "1") + + if db.Exists("/testing") { + t.Fatalf("/tesing should not exist") + } + +} diff --git a/links.go b/links.go index 9b3d0b8886..f1087ec34a 100644 --- a/links.go +++ b/links.go @@ -54,7 +54,7 @@ func (l *Link) ToEnv() []string { alias := strings.ToUpper(l.Alias()) if p := l.getDefaultPort(); p != nil { - env = append(env, fmt.Sprintf("%s_PORT=%s:%s", alias, l.ChildIP, p.Port())) + env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", alias, p.Proto(), l.ChildIP, p.Port())) } // Load exposed ports into the environment diff --git a/links_test.go b/links_test.go index 0865732890..4371248fb1 100644 --- a/links_test.go +++ b/links_test.go @@ -89,7 +89,7 @@ func TestLinkEnv(t *testing.T) { } env[parts[0]] = parts[1] } - if env["DOCKER_PORT"] != "172.0.17.2:6379" { + if env["DOCKER_PORT"] != "tcp://172.0.17.2:6379" { t.Fatalf("Expected 172.0.17.2:6379, got %s", env["DOCKER_PORT"]) } if env["DOCKER_PORT_6379_TCP"] != "tcp://172.0.17.2:6379" { diff --git a/names-generator/names-generator.go b/namesgenerator/names-generator.go similarity index 100% rename from names-generator/names-generator.go rename to namesgenerator/names-generator.go diff --git a/names-generator/names-generator_test.go b/namesgenerator/names-generator_test.go similarity index 100% rename from names-generator/names-generator_test.go rename to namesgenerator/names-generator_test.go diff --git a/runtime.go b/runtime.go index 58c2007f7b..e5c535f9fc 100644 --- a/runtime.go +++ b/runtime.go @@ -265,7 +265,10 @@ func (runtime *Runtime) restore() error { // Any containers that are left over do not exist in the graph for _, container := range containers { // Try to set the default name for a container if it exists prior to links - if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", container.ID), container.ID); err != nil { + name := generateRandomName(runtime) + container.Name = name + + if _, err := runtime.containerGraph.Set(name, container.ID); err != nil { utils.Debugf("Setting default id - %s", err) } register(container) @@ -306,8 +309,8 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) { } } -// Create creates a new container from the given configuration. -func (runtime *Runtime) Create(config *Config) (*Container, []string, error) { +// Create creates a new container from the given configuration with a given name. +func (runtime *Runtime) Create(config *Config, name string) (*Container, []string, error) { // Lookup image img, err := runtime.repositories.LookupImage(config.Image) if err != nil { @@ -345,8 +348,15 @@ func (runtime *Runtime) Create(config *Config) (*Container, []string, error) { // Generate id id := GenerateID() - // Set the default enitity in the graph - if _, err := runtime.containerGraph.Set(fmt.Sprintf("/%s", id), id); err != nil { + if name == "" { + name = generateRandomName(runtime) + } + if name[0] != '/' { + name = "/" + name + } + + // Set the enitity in the graph using the default name specified + if _, err := runtime.containerGraph.Set(name, id); err != nil { return nil, nil, err } @@ -378,6 +388,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, []string, error) { NetworkSettings: &NetworkSettings{}, // FIXME: do we need to store this in the container? SysInitPath: sysInitPath, + Name: name, } container.root = runtime.containerRoot(container.ID) // Step 1: create the container directory. @@ -483,16 +494,7 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a return img, nil } -// Strip the leading slash from the name to look up if it -// is a truncated id -// Prepend the slash back after finding the name func (runtime *Runtime) getFullName(name string) string { - if name[0] == '/' { - name = name[1:] - } - if id, err := runtime.idIndex.Get(name); err == nil { - name = id - } if name[0] != '/' { name = "/" + name } @@ -500,9 +502,7 @@ func (runtime *Runtime) getFullName(name string) string { } func (runtime *Runtime) GetByName(name string) (*Container, error) { - name = runtime.getFullName(name) - - entity := runtime.containerGraph.Get(name) + entity := runtime.containerGraph.Get(runtime.getFullName(name)) if entity == nil { return nil, fmt.Errorf("Could not find entity for %s", name) } @@ -514,6 +514,7 @@ func (runtime *Runtime) GetByName(name string) (*Container, error) { } func (runtime *Runtime) Children(name string) (map[string]*Container, error) { + name = runtime.getFullName(name) children := make(map[string]*Container) err := runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error { @@ -531,44 +532,13 @@ func (runtime *Runtime) Children(name string) (map[string]*Container, error) { return children, nil } -func (runtime *Runtime) RenameLink(oldName, newName string) error { - oldName = runtime.getFullName(oldName) - - entity := runtime.containerGraph.Get(oldName) - if entity == nil { - return fmt.Errorf("Could not find entity for %s", oldName) - } - - if newName[0] != '/' { - newName = "/" + newName - } - - // This is not rename but adding a new link for the default name - // Strip the leading '/' - if entity.ID() == oldName[1:] { - _, err := runtime.containerGraph.Set(newName, entity.ID()) +func (runtime *Runtime) RegisterLink(parent, child *Container, alias string) error { + fullName := path.Join(parent.Name, alias) + if !runtime.containerGraph.Exists(fullName) { + _, err := runtime.containerGraph.Set(fullName, child.ID) return err } - return runtime.containerGraph.Rename(oldName, newName) -} - -func (runtime *Runtime) Link(parentName, childName, alias string) error { - if id, err := runtime.idIndex.Get(parentName); err == nil { - parentName = id - } - parent := runtime.containerGraph.Get(parentName) - if parent == nil { - return fmt.Errorf("Could not get container for %s", parentName) - } - if id, err := runtime.idIndex.Get(childName); err == nil { - childName = id - } - child := runtime.containerGraph.Get(childName) - if child == nil { - return fmt.Errorf("Could not get container for %s", childName) - } - _, err := runtime.containerGraph.Set(path.Join(parentName, alias), child.ID()) - return err + return nil } // FIXME: harmonize with NewGraph() diff --git a/runtime_test.go b/runtime_test.go index 085e5be1a3..c7cea75616 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -197,6 +197,7 @@ func TestRuntimeCreate(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, + "", ) if err != nil { t.Fatal(err) @@ -234,7 +235,7 @@ func TestRuntimeCreate(t *testing.T) { } // Make sure create with bad parameters returns an error - if _, _, err = runtime.Create(&Config{Image: GetTestImage(runtime).ID}); err == nil { + if _, _, err = runtime.Create(&Config{Image: GetTestImage(runtime).ID}, ""); err == nil { t.Fatal("Builder.Create should throw an error when Cmd is missing") } @@ -243,6 +244,7 @@ func TestRuntimeCreate(t *testing.T) { Image: GetTestImage(runtime).ID, Cmd: []string{}, }, + "", ); err == nil { t.Fatal("Builder.Create should throw an error when Cmd is empty") } @@ -252,7 +254,7 @@ func TestRuntimeCreate(t *testing.T) { Cmd: []string{"/bin/ls"}, PortSpecs: []string{"80"}, } - container, _, err = runtime.Create(config) + container, _, err = runtime.Create(config, "") _, err = runtime.Commit(container, "testrepo", "testtag", "", "", config) if err != nil { @@ -267,7 +269,7 @@ func TestDestroy(t *testing.T) { container, _, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, - }) + }, "") if err != nil { t.Fatal(err) } @@ -361,7 +363,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, Cmd: []string{"sh", "-c", cmd}, PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, ExposedPorts: ep, - }) + }, "") if err != nil { nuke(runtime) t.Fatal(err) @@ -573,6 +575,9 @@ func TestReloadContainerLinks(t *testing.T) { h1 := &HostConfig{} // Add a link to container 2 h1.Links = []string{"/" + container2.ID + ":first"} + if err := runtime1.RegisterLink(container1, container2, "first"); err != nil { + t.Fatal(err) + } if err := container1.Start(h1); err != nil { t.Fatal(err) } @@ -620,7 +625,7 @@ func TestReloadContainerLinks(t *testing.T) { t.Logf("Number of links: %d", runtime2.containerGraph.Refs("0")) // Verify that the link is still registered in the runtime - entity := runtime2.containerGraph.Get(fmt.Sprintf("/%s", container1.ID)) + entity := runtime2.containerGraph.Get(container1.Name) if entity == nil { t.Fatal("Entity should not be nil") } @@ -636,13 +641,17 @@ func TestDefaultContainerName(t *testing.T) { t.Fatal(err) } - shortId, _, err := srv.ContainerCreate(config) + shortId, _, err := srv.ContainerCreate(config, "some_name") if err != nil { t.Fatal(err) } container := runtime.Get(shortId) containerID := container.ID + if container.Name != "/some_name" { + t.Fatalf("Expect /some_name got %s", container.Name) + } + paths := runtime.containerGraph.RefPaths(containerID) if paths == nil || len(paths) == 0 { t.Fatalf("Could not find edges for %s", containerID) @@ -654,12 +663,12 @@ func TestDefaultContainerName(t *testing.T) { if edge.EntityID != containerID { t.Fatalf("Expected %s got %s", containerID, edge.EntityID) } - if edge.Name != containerID { - t.Fatalf("Expected %s got %s", containerID, edge.Name) + if edge.Name != "some_name" { + t.Fatalf("Expected some_name got %s", edge.Name) } } -func TestDefaultContainerRename(t *testing.T) { +func TestRandomContainerName(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} @@ -669,24 +678,30 @@ func TestDefaultContainerRename(t *testing.T) { t.Fatal(err) } - shortId, _, err := srv.ContainerCreate(config) + shortId, _, err := srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } container := runtime.Get(shortId) containerID := container.ID - if err := runtime.RenameLink(fmt.Sprintf("/%s", containerID), "/webapp"); err != nil { - t.Fatal(err) + if container.Name == "" { + t.Fatalf("Expected not empty container name") } - webapp, err := runtime.GetByName("/webapp") - if err != nil { - t.Fatal(err) + paths := runtime.containerGraph.RefPaths(containerID) + if paths == nil || len(paths) == 0 { + t.Fatalf("Could not find edges for %s", containerID) } - - if webapp.ID != container.ID { - t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) + edge := paths[0] + if edge.ParentID != "0" { + t.Fatalf("Expected engine got %s", edge.ParentID) + } + if edge.EntityID != containerID { + t.Fatalf("Expected %s got %s", containerID, edge.EntityID) + } + if edge.Name == "" { + t.Fatalf("Expected not empty container name") } } @@ -700,16 +715,12 @@ func TestLinkChildContainer(t *testing.T) { t.Fatal(err) } - shortId, _, err := srv.ContainerCreate(config) + shortId, _, err := srv.ContainerCreate(config, "/webapp") if err != nil { t.Fatal(err) } container := runtime.Get(shortId) - if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil { - t.Fatal(err) - } - webapp, err := runtime.GetByName("/webapp") if err != nil { t.Fatal(err) @@ -724,17 +735,14 @@ func TestLinkChildContainer(t *testing.T) { t.Fatal(err) } - shortId, _, err = srv.ContainerCreate(config) + shortId, _, err = srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } childContainer := runtime.Get(shortId) - if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil { - t.Fatal(err) - } - if err := runtime.Link("/webapp", "/db", "db"); err != nil { + if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil { t.Fatal(err) } @@ -758,16 +766,12 @@ func TestGetAllChildren(t *testing.T) { t.Fatal(err) } - shortId, _, err := srv.ContainerCreate(config) + shortId, _, err := srv.ContainerCreate(config, "/webapp") if err != nil { t.Fatal(err) } container := runtime.Get(shortId) - if err := runtime.RenameLink(fmt.Sprintf("/%s", container.ID), "/webapp"); err != nil { - t.Fatal(err) - } - webapp, err := runtime.GetByName("/webapp") if err != nil { t.Fatal(err) @@ -782,17 +786,14 @@ func TestGetAllChildren(t *testing.T) { t.Fatal(err) } - shortId, _, err = srv.ContainerCreate(config) + shortId, _, err = srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } childContainer := runtime.Get(shortId) - if err := runtime.RenameLink(fmt.Sprintf("/%s", childContainer.ID), "/db"); err != nil { - t.Fatal(err) - } - if err := runtime.Link("/webapp", "/db", "db"); err != nil { + if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index d1bbe1bfe3..6cbb6db340 100644 --- a/server.go +++ b/server.go @@ -156,7 +156,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return "", err } - c, _, err := srv.runtime.Create(config) + c, _, err := srv.runtime.Create(config, "") if err != nil { return "", err } @@ -937,7 +937,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write return nil } -func (srv *Server) ContainerCreate(config *Config) (string, []string, error) { +func (srv *Server) ContainerCreate(config *Config, name string) (string, []string, error) { if config.Memory != 0 && config.Memory < 524288 { return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)") } @@ -949,7 +949,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, []string, error) { if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { config.MemorySwap = -1 } - container, buildWarnings, err := srv.runtime.Create(config) + container, buildWarnings, err := srv.runtime.Create(config, name) if err != nil { if srv.runtime.graph.IsNotExist(err) { @@ -1209,13 +1209,12 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) return nil, nil } -func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { +func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { runtime := srv.runtime container := runtime.Get(name) if container == nil { return fmt.Errorf("No such container: %s", name) } - // Register links if hostConfig != nil && hostConfig.Links != nil { for _, l := range hostConfig.Links { @@ -1223,16 +1222,28 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if err != nil { return err } - - childName := parts["name"] - if childName[0] != '/' { - childName = "/" + childName + child, err := srv.runtime.GetByName(parts["name"]) + if err != nil { + return err } - if err := runtime.Link(fmt.Sprintf("/%s", container.ID), childName, parts["alias"]); err != nil { + if child == nil { + return fmt.Errorf("Could not get container for %s", parts["name"]) + } + + if err := runtime.RegisterLink(container, child, parts["alias"]); err != nil { return err } } } + return nil +} + +func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { + runtime := srv.runtime + container := runtime.Get(name) + if container == nil { + return fmt.Errorf("No such container: %s", name) + } if err := container.Start(hostConfig); err != nil { return fmt.Errorf("Error starting container %s: %s", name, err) diff --git a/server_test.go b/server_test.go index ef7b1a9106..7a8ea4e41c 100644 --- a/server_test.go +++ b/server_test.go @@ -89,7 +89,7 @@ func TestCreateRm(t *testing.T) { t.Fatal(err) } - id, _, err := srv.ContainerCreate(config) + id, _, err := srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } @@ -119,7 +119,7 @@ func TestCreateRmVolumes(t *testing.T) { t.Fatal(err) } - id, _, err := srv.ContainerCreate(config) + id, _, err := srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } @@ -158,7 +158,7 @@ func TestCommit(t *testing.T) { t.Fatal(err) } - id, _, err := srv.ContainerCreate(config) + id, _, err := srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } @@ -179,7 +179,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - id, _, err := srv.ContainerCreate(config) + id, _, err := srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } @@ -231,6 +231,7 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) { CpuShares: 1000, Cmd: []string{"/bin/cat"}, }, + "", ); err == nil { t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") } @@ -397,7 +398,7 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - containerID, _, err := srv.ContainerCreate(config) + containerID, _, err := srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } @@ -418,7 +419,7 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - containerID, _, err = srv.ContainerCreate(config) + containerID, _, err = srv.ContainerCreate(config, "") if err != nil { t.Fatal(err) } diff --git a/sorter.go b/sorter.go index ad931ebcb1..d4331eaf1f 100644 --- a/sorter.go +++ b/sorter.go @@ -81,25 +81,3 @@ func sortContainers(containers []*Container, predicate func(i, j *Container) boo s := &containerSorter{containers, predicate} sort.Sort(s) } - -type apiLinkSorter struct { - links []APILink - by func(i, j APILink) bool -} - -func (s *apiLinkSorter) Len() int { - return len(s.links) -} - -func (s *apiLinkSorter) Swap(i, j int) { - s.links[i], s.links[j] = s.links[j], s.links[i] -} - -func (s *apiLinkSorter) Less(i, j int) bool { - return s.by(s.links[i], s.links[j]) -} - -func sortLinks(links []APILink, predicate func(i, j APILink) bool) { - s := &apiLinkSorter{links, predicate} - sort.Sort(s) -} diff --git a/utils.go b/utils.go index 6da1c0aee6..cffee05615 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "github.com/dotcloud/docker/namesgenerator" "github.com/dotcloud/docker/utils" "strconv" "strings" @@ -289,3 +290,20 @@ func migratePortMappings(config *Config) error { func parseLink(rawLink string) (map[string]string, error) { return utils.PartParser("name:alias", rawLink) } + +type checker struct { + runtime *Runtime +} + +func (c *checker) Exists(name string) bool { + return c.runtime.containerGraph.Exists("/" + name) +} + +// Generate a random and unique name +func generateRandomName(runtime *Runtime) string { + n, err := namesgenerator.GenerateRandomName(&checker{runtime}) + if err != nil { + panic(err) + } + return n +} diff --git a/utils_test.go b/utils_test.go index 917c4c1591..439cf0a7d1 100644 --- a/utils_test.go +++ b/utils_test.go @@ -129,7 +129,7 @@ func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConf if config.Image == "_" { config.Image = GetTestImage(r).ID } - c, _, err := r.Create(config) + c, _, err := r.Create(config, "") if err != nil { return nil, nil, err } From 7e8b2c3836fffcd1e69edfbd8873c461bda419c7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 28 Oct 2013 19:19:31 -0700 Subject: [PATCH 40/40] Make sure only links are removed and not names --- gograph/gograph.go | 3 +++ gograph/gograph_test.go | 14 +++++++++++++- server.go | 30 ++++++++++++++++++------------ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/gograph/gograph.go b/gograph/gograph.go index ff0b854064..94ce90afb4 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -179,6 +179,9 @@ func (db *Database) get(name string) (*Entity, error) { parts := split(name) for i := 1; i < len(parts); i++ { p := parts[i] + if p == "" { + continue + } next := db.child(e, p) if next == nil { diff --git a/gograph/gograph_test.go b/gograph/gograph_test.go index 7107ef55ea..dba8ed1080 100644 --- a/gograph/gograph_test.go +++ b/gograph/gograph_test.go @@ -482,10 +482,22 @@ func TestExistsFalse(t *testing.T) { db, dbpath := newTestDb(t) defer destroyTestDb(dbpath) - db.Set("toerhe", "1") + db.Set("/toerhe", "1") if db.Exists("/testing") { t.Fatalf("/tesing should not exist") } } + +func TestGetNameWithTrailingSlash(t *testing.T) { + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) + + db.Set("/todo", "1") + + e := db.Get("/todo/") + if e == nil { + t.Fatalf("Entity should not be nil") + } +} diff --git a/server.go b/server.go index 6cbb6db340..2cb6771178 100644 --- a/server.go +++ b/server.go @@ -990,20 +990,17 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) parent, n := path.Split(name) pe := srv.runtime.containerGraph.Get(parent) - if pe != nil { - parentContainer := srv.runtime.Get(pe.ID()) - - if parentContainer != nil && parentContainer.activeLinks != nil { - if link, exists := parentContainer.activeLinks[n]; exists { - link.Disable() - } else { - utils.Debugf("Could not find active link for %s", name) - } - } + if pe == nil { + return fmt.Errorf("Cannot get parent %s for name %s", parent, name) } + parentContainer := srv.runtime.Get(pe.ID()) - if name[1:] == container.ID { - return fmt.Errorf("Conflict, cannot remove default link %s", name) + if parentContainer != nil && parentContainer.activeLinks != nil { + if link, exists := parentContainer.activeLinks[n]; exists { + link.Disable() + } else { + utils.Debugf("Could not find active link for %s", name) + } } if err := srv.runtime.containerGraph.Delete(name); err != nil { @@ -1011,6 +1008,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) } return nil } + if container != nil { if container.State.Running { return fmt.Errorf("Impossible to remove a running container, please stop it first") @@ -1215,6 +1213,7 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { if container == nil { return fmt.Errorf("No such container: %s", name) } + // Register links if hostConfig != nil && hostConfig.Links != nil { for _, l := range hostConfig.Links { @@ -1234,6 +1233,13 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error { return err } } + + // After we load all the links into the runtime + // set them to nil on the hostconfig + hostConfig.Links = nil + if err := container.SaveHostConfig(hostConfig); err != nil { + return err + } } return nil }