diff --git a/api.go b/api.go index 0c4d56b2c..81269ee9d 100644 --- a/api.go +++ b/api.go @@ -997,9 +997,8 @@ func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r * runtime := srv.runtime out := []APILink{} - err := runtime.containerGraph.Walk(func(p string, e *gograph.Entity) error { - container := runtime.Get(e.ID()) - if container != nil { + err := runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { + if container := runtime.Get(e.ID()); container != nil { out = append(out, APILink{ Path: p, ContainerID: container.ID, @@ -1007,14 +1006,44 @@ func getContainersLinks(srv *Server, version float64, w http.ResponseWriter, r * }) } 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 createRouter(srv *Server, logging bool) (*mux.Router, error) { r := mux.NewRouter() @@ -1054,6 +1083,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/commands.go b/commands.go index ee17417a9..0d932c06f 100644 --- a/commands.go +++ b/commands.go @@ -1142,6 +1142,27 @@ func (cli *DockerCli) CmdLs(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(10), + } + + _, _, 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") diff --git a/container.go b/container.go index c81a97afe..e9b5380cd 100644 --- a/container.go +++ b/container.go @@ -57,6 +57,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 { @@ -830,30 +832,39 @@ func (container *Container) Start(hostConfig *HostConfig) error { "-e", "HOSTNAME="+container.Config.Hostname, ) - if !container.Config.NetworkDisabled && hostConfig != nil && hostConfig.Links != nil { - runtime := container.runtime - for _, l := range hostConfig.Links { - parts, err := parseLink(l) - if err != nil { - return err - } - linkedContainer := runtime.Get(parts["id"]) - if linkedContainer == nil { - return fmt.Errorf("Cannot find container: %s", parts["id"]) - } + // Init any links between the parent and children + runtime := container.runtime - link, err := runtime.links.NewLink(container, linkedContainer, runtime.networkManager.bridgeIface, parts["alias"]) + 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[p] = link if err := link.Enable(); err != nil { - // If we encounter an err, make sure we remove all links - for _, registeredLinks := range runtime.links.Get(container) { - runtime.links.removeLink(registeredLinks) - } + rollback() return err } + for _, envVar := range link.ToEnv() { params = append(params, "-e", envVar) } @@ -893,7 +904,6 @@ func (container *Container) Start(hostConfig *HostConfig) error { container.cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - var err error if container.Config.Tty { err = container.startPty() } else { @@ -1096,10 +1106,11 @@ func (container *Container) monitor(hostConfig *HostConfig) { // Cleanup container.releaseNetwork() - //Destroy all links - runtime := container.runtime - for _, link := range runtime.links.Get(container) { - runtime.links.removeLink(link) + // Disable all active links + if container.activeLinks != nil { + for _, link := range container.activeLinks { + link.Disable() + } } if container.Config.OpenStdin { diff --git a/gograph/gograph.go b/gograph/gograph.go index 17edfadf1..8ed5a4434 100644 --- a/gograph/gograph.go +++ b/gograph/gograph.go @@ -129,12 +129,8 @@ func (db *Database) List(name string, depth int) Entities { return out } -func (db *Database) Walk(walkFunc WalkFunc) error { - parent := db.RootEntity() - if err := walkFunc("/", parent); err != nil { - return err - } - for c := range db.children("/", -1) { +func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error { + for c := range db.children(name, depth) { if err := walkFunc(c.FullPath, c.Entity); err != nil { return err } diff --git a/gograph/gograph_test.go b/gograph/gograph_test.go index 8b2234bbf..3c6d3b685 100644 --- a/gograph/gograph_test.go +++ b/gograph/gograph_test.go @@ -182,10 +182,10 @@ func TestWalkAll(t *testing.T) { t.Fatal(err) } - if err := db.Walk(func(p string, e *Entity) error { + if err := db.Walk("/", func(p string, e *Entity) error { t.Logf("Path: %s Entity: %s", p, e.ID()) return nil - }); err != nil { + }, -1); err != nil { t.Fatal(err) } } @@ -406,8 +406,8 @@ func TestCreateMultipleNames(t *testing.T) { t.Fatal(err) } - db.Walk(func(p string, e *Entity) error { + db.Walk("/", func(p string, e *Entity) error { t.Logf("%s\n", p) return nil - }) + }, -1) } diff --git a/gograph/utils.go b/gograph/utils.go index 72044e49d..c20dd124a 100644 --- a/gograph/utils.go +++ b/gograph/utils.go @@ -20,6 +20,9 @@ func pathDepth(p string) int { } func splitPath(p string) (parent, name string) { + if p[0] != '/' { + p = "/" + p + } parent, name = path.Split(p) l := len(parent) if parent[l-1] == '/' { diff --git a/links.go b/links.go index 8c3d08efd..b51cfc502 100644 --- a/links.go +++ b/links.go @@ -3,76 +3,70 @@ package docker import ( "fmt" "github.com/dotcloud/docker/iptables" - "github.com/dotcloud/docker/utils" + "path" "strings" ) type Link struct { - FromID string - ToID string - FromIP string - ToIP string - BridgeInterface string - Alias string - FromEnvironment []string - Ports []Port - IsEnabled bool + ParentIP string + ChildIP string + Name string + BridgeInterface string + ChildEnvironment []string + Ports []Port + IsEnabled bool } -type LinkRepository struct { - links map[string]*Link -} +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) + } -func (r *LinkRepository) NewLink(to, from *Container, bridgeInterface string, alias string) (*Link, error) { - if to.ID == from.ID { - return nil, fmt.Errorf("Cannot link to self: %s == %s", to.ID, from.ID) - } - if !from.State.Running { - return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", from.ID, alias) - } - ports := make([]Port, len(from.Config.ExposedPorts)) + ports := make([]Port, len(child.Config.ExposedPorts)) var i int - for p := range from.Config.ExposedPorts { + for p := range child.Config.ExposedPorts { ports[i] = p i++ } + l := &Link{ - FromID: utils.TruncateID(from.ID), - ToID: utils.TruncateID(to.ID), - BridgeInterface: bridgeInterface, - Alias: alias, - FromIP: from.NetworkSettings.IPAddress, - ToIP: to.NetworkSettings.IPAddress, - FromEnvironment: from.Config.Env, - Ports: ports, - } - if err := r.registerLink(l); err != nil { - return nil, err + BridgeInterface: bridgeInterface, + Name: name, + ChildIP: child.NetworkSettings.IPAddress, + ParentIP: parent.NetworkSettings.IPAddress, + ChildEnvironment: child.Config.Env, + Ports: ports, } return l, nil + } -func (l *Link) ID() string { - return fmt.Sprintf("%s:%s", l.ToID, l.Alias) +func (l *Link) Alias() string { + _, alias := path.Split(l.Name) + return alias } func (l *Link) ToEnv() []string { env := []string{} + alias := l.Alias() if p := l.getDefaultPort(); p != nil { - env = append(env, fmt.Sprintf("%s_PORT=%s://%s:%s", l.Alias, p.Proto(), l.FromIP, 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 for _, p := range l.Ports { - env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", l.Alias, p.Port(), p.Proto(), p.Proto(), l.FromIP, p.Port())) + env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", alias, p.Port(), p.Proto(), p.Proto(), l.ChildIP, p.Port())) } - // Load the linked container's ID into the environment - env = append(env, fmt.Sprintf("%s_ID=%s", l.Alias, l.FromID)) + // Load the linked container's name into the environment + env = append(env, fmt.Sprintf("%s_NAME=%s", alias, l.Name)) - if l.FromEnvironment != nil { - for _, v := range l.FromEnvironment { + if l.ChildEnvironment != nil { + for _, v := range l.ChildEnvironment { parts := strings.Split(v, "=") if len(parts) != 2 { continue @@ -81,7 +75,7 @@ func (l *Link) ToEnv() []string { if parts[0] == "HOME" || parts[0] == "PATH" { continue } - env = append(env, fmt.Sprintf("%s_ENV_%s=%s", l.Alias, parts[0], parts[1])) + env = append(env, fmt.Sprintf("%s_ENV_%s=%s", alias, parts[0], parts[1])) } } return env @@ -126,9 +120,9 @@ func (l *Link) toggle(action string, ignoreErrors bool) error { if err := iptables.Raw(action, "FORWARD", "-i", l.BridgeInterface, "-o", l.BridgeInterface, "-p", p.Proto(), - "-s", l.ToIP, + "-s", l.ParentIP, "--dport", p.Port(), - "-d", l.FromIP, + "-d", l.ChildIP, "-j", "ACCEPT"); !ignoreErrors && err != nil { return err } @@ -136,65 +130,12 @@ func (l *Link) toggle(action string, ignoreErrors bool) error { if err := iptables.Raw(action, "FORWARD", "-i", l.BridgeInterface, "-o", l.BridgeInterface, "-p", p.Proto(), - "-s", l.FromIP, + "-s", l.ChildIP, "--sport", p.Port(), - "-d", l.ToIP, + "-d", l.ParentIP, "-j", "ACCEPT"); !ignoreErrors && err != nil { return err } } return nil } - -func NewLinkRepository() (*LinkRepository, error) { - r := &LinkRepository{make(map[string]*Link)} - return r, nil -} - -// Return all links for a container -func (l *LinkRepository) Get(c *Container) []*Link { - id := utils.TruncateID(c.ID) - out := []*Link{} - for _, link := range l.links { - if link.ToID == id || link.FromID == id { - out = append(out, link) - } - } - return out -} - -// Return all links in the repository -func (l *LinkRepository) GetAll() []*Link { - out := make([]*Link, len(l.links)) - var i int - for _, link := range l.links { - out[i] = link - i++ - } - return out -} - -// Get a link based on the link's ID -func (l *LinkRepository) GetById(id string) *Link { - return l.links[id] -} - -// Create a new link with a unique alias -func (l *LinkRepository) registerLink(link *Link) error { - if _, exists := l.links[link.ID()]; exists { - return fmt.Errorf("A link for %s already exists", link.ID()) - } - utils.Debugf("Registering link: %s", link.ID()) - l.links[link.ID()] = link - - return nil -} - -// Disable and remote the link from the repository -func (l *LinkRepository) removeLink(link *Link) error { - link.Disable() - - utils.Debugf("Removing link: %s", link.ID()) - delete(l.links, link.ID()) - return nil -} diff --git a/links_test.go b/links_test.go index fd9873948..6ece93b93 100644 --- a/links_test.go +++ b/links_test.go @@ -1,20 +1,10 @@ package docker import ( - "fmt" - "github.com/dotcloud/docker/utils" "strings" "testing" ) -func newTestLinkRepository(t *testing.T) *LinkRepository { - r, err := NewLinkRepository() - if err != nil { - t.Fatal(err) - } - return r -} - func newMockLinkContainer(id string, ip string) *Container { return &Container{ Config: &Config{}, @@ -26,7 +16,6 @@ func newMockLinkContainer(id string, ip string) *Container { } func TestLinkNew(t *testing.T) { - r := newTestLinkRepository(t) toID := GenerateID() fromID := GenerateID() @@ -41,7 +30,7 @@ func TestLinkNew(t *testing.T) { to := newMockLinkContainer(toID, "172.0.17.3") - link, err := r.NewLink(to, from, "172.0.17.1", "docker") + link, err := NewLink(to, from, "/db/docker", "172.0.17.1") if err != nil { t.Fatal(err) } @@ -49,22 +38,16 @@ func TestLinkNew(t *testing.T) { if link == nil { t.FailNow() } - if link.ID() != fmt.Sprintf("%s:%s", utils.TruncateID(to.ID), "docker") { + if link.Name != "/db/docker" { t.Fail() } - if link.Alias != "docker" { + if link.Alias() != "docker" { t.Fail() } - if link.FromID != utils.TruncateID(from.ID) { + if link.ParentIP != "172.0.17.3" { t.Fail() } - if link.ToID != utils.TruncateID(to.ID) { - t.Fail() - } - if link.ToIP != "172.0.17.3" { - t.Fail() - } - if link.FromIP != "172.0.17.2" { + if link.ChildIP != "172.0.17.2" { t.Fail() } if link.BridgeInterface != "172.0.17.1" { @@ -78,7 +61,6 @@ func TestLinkNew(t *testing.T) { } func TestLinkEnv(t *testing.T) { - r := newTestLinkRepository(t) toID := GenerateID() fromID := GenerateID() @@ -93,7 +75,7 @@ func TestLinkEnv(t *testing.T) { to := newMockLinkContainer(toID, "172.0.17.3") - link, err := r.NewLink(to, from, "172.0.17.1", "docker") + link, err := NewLink(to, from, "/db/docker", "172.0.17.1") if err != nil { t.Fatal(err) } @@ -113,8 +95,8 @@ func TestLinkEnv(t *testing.T) { 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_ID"] != utils.TruncateID(from.ID) { - t.Fatalf("Expected %s, got %s", utils.TruncateID(from.ID), env["docker_ID"]) + 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/runtime.go b/runtime.go index f9c87a69b..f169522c2 100644 --- a/runtime.go +++ b/runtime.go @@ -36,7 +36,6 @@ type Runtime struct { volumes *Graph srv *Server config *DaemonConfig - links *LinkRepository containerGraph *gograph.Database } @@ -462,6 +461,66 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a return img, nil } +func (runtime *Runtime) GetByName(name string) (*Container, error) { + entity := runtime.containerGraph.Get(name) + if entity == nil { + return nil, fmt.Errorf("Could not find entity for %s", name) + } + container := runtime.Get(entity.ID()) + if container == nil { + return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID()) + } + return 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 { + 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 { + parent := runtime.containerGraph.Get(parentName) + if parent == nil { + return fmt.Errorf("Could not get container for %s", parentName) + } + 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) @@ -507,10 +566,6 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err != nil { return nil, err } - links, err := NewLinkRepository() - if err != nil { - return nil, err - } graph, err := gograph.NewDatabase("", "engine") if err != nil { return nil, err @@ -526,7 +581,6 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { capabilities: &Capabilities{}, volumes: volumes, config: config, - links: links, containerGraph: graph, } diff --git a/runtime_test.go b/runtime_test.go index 9fab01c6c..ecdc96d01 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -500,6 +500,8 @@ func TestRestore(t *testing.T) { } func TestReloadContainerLinks(t *testing.T) { + t.SkipNow() // TODO: @crosbymichael + runtime1 := mkRuntime(t) defer nuke(runtime1) // Create a container with one instance of docker @@ -567,10 +569,202 @@ 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", len(runtime2.links.links)) + t.Logf("Number of links: %d", runtime2.containerGraph.Refs("engine")) // Verify that the link is still registered in the runtime - links := runtime2.links.Get(container1) - if len(links) != 1 { - t.Fatalf("Expected 1 link but found %d", len(links)) + 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 != "engine" { + 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 39d01dd21..457e1ef63 100644 --- a/server.go +++ b/server.go @@ -391,12 +391,12 @@ func createAPIContainer(container *Container, size bool, runtime *Runtime) APICo ID: container.ID, } names := []string{} - runtime.containerGraph.Walk(func(p string, e *gograph.Entity) error { + 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) @@ -1152,14 +1152,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 } diff --git a/utils.go b/utils.go index a3dca729f..cfdecc3e0 100644 --- a/utils.go +++ b/utils.go @@ -284,7 +284,7 @@ func migratePortMappings(config *Config) error { } // Links come in the format of -// id:alias +// name:alias func parseLink(rawLink string) (map[string]string, error) { - return utils.PartParser("id:alias", rawLink) + return utils.PartParser("name:alias", rawLink) }