Refactor to use graph for linking

This commit is contained in:
Michael Crosby
2013-09-30 12:33:25 -07:00
parent e4fbeddf53
commit 457a9260bc
12 changed files with 431 additions and 181 deletions
+35 -5
View File
@@ -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,
+21
View File
@@ -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")
+32 -21
View File
@@ -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 {
+2 -6
View File
@@ -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
}
+4 -4
View File
@@ -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)
}
+3
View File
@@ -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] == '/' {
+40 -99
View File
@@ -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
}
+8 -26
View File
@@ -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"])
+60 -6
View File
@@ -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,
}
+198 -4
View File
@@ -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)
}
}
}
+26 -8
View File
@@ -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
}
+2 -2
View File
@@ -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)
}