diff --git a/links.go b/links.go index 48ec2880f..e44c07319 100644 --- a/links.go +++ b/links.go @@ -17,8 +17,8 @@ type Link struct { BridgeInterface string Alias string FromEnvironment []string - ports []Port - isEnabled bool + Ports []Port + IsEnabled bool } type LinkRepository struct { @@ -34,8 +34,8 @@ func (r *LinkRepository) NewLink(to, from *Container, bridgeInterface string, al } ports := make([]Port, len(from.Config.ExposedPorts)) var i int - for k := range from.Config.ExposedPorts { - ports[i] = k + for p := range from.Config.ExposedPorts { + ports[i] = p i++ } l := &Link{ @@ -46,7 +46,7 @@ func (r *LinkRepository) NewLink(to, from *Container, bridgeInterface string, al FromIP: from.NetworkSettings.IPAddress, ToIP: to.NetworkSettings.IPAddress, FromEnvironment: from.Config.Env, - ports: ports, + Ports: ports, } if err := r.registerLink(l); err != nil { return nil, err @@ -66,7 +66,7 @@ func (l *Link) ToEnv() []string { } // Load exposed ports into the environment - for _, p := range l.ports { + for _, p := range l.Ports { env = append(env, fmt.Sprintf("%s_PORT_%s_%s=%s://%s:%s", l.Alias, p.Port(), strings.ToUpper(p.Proto()), p.Proto(), l.FromIP, p.Port())) } @@ -92,18 +92,18 @@ func (l *Link) ToEnv() []string { // Default port rules func (l *Link) getDefaultPort() *Port { var p Port - i := len(l.ports) + i := len(l.Ports) if i == 0 { return nil } else if i > 1 { - sortPorts(l.ports, func(ip, jp Port) bool { + 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() && ip.Proto() == "tcp") }) } - p = l.ports[0] + p = l.Ports[0] return &p } @@ -111,7 +111,7 @@ func (l *Link) Enable() error { if err := l.toggle("-I", false); err != nil { return err } - l.isEnabled = true + l.IsEnabled = true return nil } @@ -120,11 +120,11 @@ func (l *Link) Disable() { // exist in iptables l.toggle("-D", true) - l.isEnabled = false + l.IsEnabled = false } func (l *Link) toggle(action string, ignoreErrors bool) error { - for _, p := range l.ports { + for _, p := range l.Ports { if err := iptables.Raw(action, "FORWARD", "-i", l.BridgeInterface, "-o", l.BridgeInterface, "-p", p.Proto(), @@ -148,7 +148,7 @@ func (l *Link) toggle(action string, ignoreErrors bool) error { return nil } -func NewLinkRepository(root string) (*LinkRepository, error) { +func NewLinkRepository() (*LinkRepository, error) { r := &LinkRepository{make(map[string]*Link)} return r, nil } diff --git a/links_test.go b/links_test.go index 887ad865e..f7d0c7a16 100644 --- a/links_test.go +++ b/links_test.go @@ -8,7 +8,7 @@ import ( ) func newTestLinkRepository(t *testing.T) *LinkRepository { - r, err := NewLinkRepository("") + r, err := NewLinkRepository() if err != nil { t.Fatal(err) } @@ -70,7 +70,7 @@ func TestLinkNew(t *testing.T) { if link.BridgeInterface != "172.0.17.1" { t.Fail() } - for _, p := range link.ports { + for _, p := range link.Ports { if p != Port("6379/tcp") { t.Fail() } diff --git a/runtime.go b/runtime.go index 040f52916..66fda5838 100644 --- a/runtime.go +++ b/runtime.go @@ -87,10 +87,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 +100,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 } @@ -152,7 +148,7 @@ func (runtime *Runtime) Register(container *Container) error { 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 } @@ -227,9 +223,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 +235,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 } @@ -482,7 +499,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err != nil { return nil, err } - links, err := NewLinkRepository("") + links, err := NewLinkRepository() if err != nil { return nil, err } diff --git a/runtime_test.go b/runtime_test.go index dd111f752..9fab01c6c 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -498,3 +498,79 @@ 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", len(runtime2.links.links)) + // 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)) + } +} diff --git a/sorter.go b/sorter.go index 09f1fcbe0..d4331eaf1 100644 --- a/sorter.go +++ b/sorter.go @@ -59,3 +59,25 @@ 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) +}