-
Notifications
You must be signed in to change notification settings - Fork 712
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Have the app periodically register with weaveDNS, and the probe do lo…
…okups there.
- Loading branch information
Showing
15 changed files
with
930 additions
and
321 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package app | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"strings" | ||
|
||
fsouza "github.com/fsouza/go-dockerclient" | ||
|
||
"github.com/weaveworks/scope/common/backoff" | ||
) | ||
|
||
// Default values for weave app integration | ||
const ( | ||
DefaultHostname = "scope.weave.local." | ||
DefaultWeaveURL = "http://127.0.0.1:6784" | ||
DefaultContainerName = "weavescope" | ||
DefaultDockerEndpoint = "unix:///var/run/docker.sock" | ||
) | ||
|
||
// WeavePublisher is a thing which periodically registers this app with WeaveDNS. | ||
type WeavePublisher struct { | ||
containerName string | ||
hostname string | ||
dockerClient DockerClient | ||
weaveClient WeaveClient | ||
backoff backoff.Interface | ||
interfaces InterfaceFunc | ||
} | ||
|
||
// DockerClient is the little bit of the docker client we need. | ||
type DockerClient interface { | ||
ListContainers(fsouza.ListContainersOptions) ([]fsouza.APIContainers, error) | ||
} | ||
|
||
// WeaveClient is the little bit of the weave clent we need. | ||
type WeaveClient interface { | ||
AddDNSEntry(hostname, containerid string, ip net.IP) error | ||
Expose() error | ||
} | ||
|
||
// Interface is because net.Interface isn't mockable. | ||
type Interface struct { | ||
Name string | ||
Addrs []net.Addr | ||
} | ||
|
||
// InterfaceFunc is the type of Interfaces() | ||
type InterfaceFunc func() ([]Interface, error) | ||
|
||
// Interfaces returns the list of Interfaces on the machine. | ||
func Interfaces() ([]Interface, error) { | ||
ifaces, err := net.Interfaces() | ||
if err != nil { | ||
return nil, err | ||
} | ||
result := []Interface{} | ||
for _, i := range ifaces { | ||
addrs, err := i.Addrs() | ||
if err != nil { | ||
continue | ||
} | ||
result = append(result, Interface{ | ||
Name: i.Name, | ||
Addrs: addrs, | ||
}) | ||
} | ||
return result, nil | ||
} | ||
|
||
// NewWeavePublisher makes a new Weave. | ||
func NewWeavePublisher(weaveClient WeaveClient, dockerClient DockerClient, interfaces InterfaceFunc, hostname, containerName string) *WeavePublisher { | ||
w := &WeavePublisher{ | ||
containerName: containerName, | ||
hostname: hostname, | ||
dockerClient: dockerClient, | ||
weaveClient: weaveClient, | ||
interfaces: interfaces, | ||
} | ||
w.backoff = backoff.New(w.updateDNS, "updating weaveDNS") | ||
go w.backoff.Start() | ||
return w | ||
} | ||
|
||
// Stop the Weave. | ||
func (w *WeavePublisher) Stop() { | ||
w.backoff.Stop() | ||
} | ||
|
||
func (w *WeavePublisher) updateDNS() (bool, error) { | ||
// 0. expose this host | ||
if err := w.weaveClient.Expose(); err != nil { | ||
return false, err | ||
} | ||
|
||
// 1. work out my IP addresses | ||
ifaces, err := w.interfaces() | ||
if err != nil { | ||
return false, err | ||
} | ||
ips := []net.IP{} | ||
for _, i := range ifaces { | ||
if strings.HasPrefix(i.Name, "lo") || | ||
strings.HasPrefix(i.Name, "docker") || | ||
strings.HasPrefix(i.Name, "veth") { | ||
continue | ||
} | ||
|
||
for _, addr := range i.Addrs { | ||
var ip net.IP | ||
switch v := addr.(type) { | ||
case *net.IPAddr: | ||
ip = v.IP | ||
case *net.IPNet: | ||
ip = v.IP | ||
} | ||
if ip != nil && ip.To4() != nil { | ||
ips = append(ips, ip) | ||
} | ||
} | ||
} | ||
|
||
// 2. work out my container name | ||
containers, err := w.dockerClient.ListContainers(fsouza.ListContainersOptions{}) | ||
if err != nil { | ||
return false, err | ||
} | ||
containerID := "" | ||
outer: | ||
for _, container := range containers { | ||
for _, name := range container.Names { | ||
if name == "/"+w.containerName { | ||
containerID = container.ID | ||
break outer | ||
} | ||
} | ||
} | ||
if containerID == "" { | ||
return false, fmt.Errorf("Container %s not found", w.containerName) | ||
} | ||
|
||
// 3. Register these with weave dns | ||
for _, ip := range ips { | ||
if err := w.weaveClient.AddDNSEntry(w.hostname, containerID, ip); err != nil { | ||
return false, err | ||
} | ||
} | ||
return false, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package app_test | ||
|
||
import ( | ||
"net" | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
fsouza "github.com/fsouza/go-dockerclient" | ||
|
||
"github.com/weaveworks/scope/app" | ||
"github.com/weaveworks/scope/test" | ||
) | ||
|
||
type mockDockerClient struct{} | ||
|
||
func (mockDockerClient) ListContainers(fsouza.ListContainersOptions) ([]fsouza.APIContainers, error) { | ||
return []fsouza.APIContainers{ | ||
{ | ||
Names: []string{"/" + containerName}, | ||
ID: containerID, | ||
}, | ||
{ | ||
Names: []string{"/notme"}, | ||
ID: "1234abcd", | ||
}, | ||
}, nil | ||
} | ||
|
||
type entry struct { | ||
containerid string | ||
ip net.IP | ||
} | ||
|
||
type mockWeaveClient struct { | ||
sync.Mutex | ||
published map[string]entry | ||
} | ||
|
||
func (m *mockWeaveClient) AddDNSEntry(hostname, containerid string, ip net.IP) error { | ||
m.Lock() | ||
defer m.Unlock() | ||
m.published[hostname] = entry{containerid, ip} | ||
return nil | ||
} | ||
|
||
func (m *mockWeaveClient) Expose() error { | ||
return nil | ||
} | ||
|
||
const ( | ||
hostname = "foo.weave" | ||
containerName = "bar" | ||
containerID = "a1b2c3d4" | ||
) | ||
|
||
var ( | ||
ip = net.ParseIP("1.2.3.4") | ||
) | ||
|
||
func TestWeave(t *testing.T) { | ||
weaveClient := &mockWeaveClient{ | ||
published: map[string]entry{}, | ||
} | ||
dockerClient := mockDockerClient{} | ||
interfaces := func() ([]app.Interface, error) { | ||
return []app.Interface{ | ||
{ | ||
Name: "eth0", | ||
Addrs: []net.Addr{ | ||
&net.IPAddr{ | ||
IP: ip, | ||
}, | ||
}, | ||
}, | ||
{ | ||
Name: "docker0", | ||
Addrs: []net.Addr{ | ||
&net.IPAddr{ | ||
IP: net.ParseIP("4.3.2.1"), | ||
}, | ||
}, | ||
}, | ||
}, nil | ||
} | ||
publisher := app.NewWeavePublisher( | ||
weaveClient, dockerClient, interfaces, | ||
hostname, containerName) | ||
defer publisher.Stop() | ||
|
||
want := map[string]entry{ | ||
hostname: {containerID, ip}, | ||
} | ||
test.Poll(t, 100*time.Millisecond, want, func() interface{} { | ||
weaveClient.Lock() | ||
defer weaveClient.Unlock() | ||
result := map[string]entry{} | ||
for k, v := range weaveClient.published { | ||
result[k] = v | ||
} | ||
return result | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package backoff | ||
|
||
import ( | ||
"log" | ||
"time" | ||
) | ||
|
||
type backoff struct { | ||
f func() (bool, error) | ||
quit, done chan struct{} | ||
msg string | ||
initialBackoff, maxBackoff time.Duration | ||
} | ||
|
||
// Interface does f in a loop, sleeping for initialBackoff between | ||
// each iterations. If it hits an error, it exponentially backs | ||
// off to maxBackoff. Backoff will log when it backs off, but | ||
// will stop logging when it reaches maxBackoff. It will also | ||
// log on first success. | ||
type Interface interface { | ||
Start() | ||
Stop() | ||
SetInitialBackoff(time.Duration) | ||
} | ||
|
||
// New makes a new Interface | ||
func New(f func() (bool, error), msg string) Interface { | ||
return &backoff{ | ||
f: f, | ||
quit: make(chan struct{}), | ||
done: make(chan struct{}), | ||
msg: msg, | ||
initialBackoff: 10 * time.Second, | ||
maxBackoff: 60 * time.Second, | ||
} | ||
} | ||
|
||
func (b *backoff) SetInitialBackoff(d time.Duration) { | ||
b.initialBackoff = d | ||
} | ||
|
||
// Stop the backoff, and waits for it to stop. | ||
func (b *backoff) Stop() { | ||
close(b.quit) | ||
<-b.done | ||
} | ||
|
||
// Start the backoff. Can only be called once. | ||
func (b *backoff) Start() { | ||
defer close(b.done) | ||
backoff := b.initialBackoff | ||
shouldLog := true | ||
|
||
for { | ||
done, err := b.f() | ||
if done { | ||
return | ||
} | ||
|
||
if err != nil { | ||
backoff *= 2 | ||
if backoff > b.maxBackoff { | ||
backoff = b.maxBackoff | ||
} | ||
} else if backoff > b.initialBackoff { | ||
backoff = b.initialBackoff | ||
shouldLog = true | ||
} | ||
|
||
if shouldLog { | ||
if err != nil { | ||
log.Printf("Error %s, backing off %s: %s", | ||
b.msg, backoff, err) | ||
} else { | ||
log.Printf("Success %s", b.msg) | ||
} | ||
} | ||
|
||
if backoff >= b.maxBackoff || err == nil { | ||
shouldLog = false | ||
} | ||
|
||
select { | ||
case <-time.After(backoff): | ||
case <-b.quit: | ||
return | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.