Skip to content

Commit

Permalink
Have the app periodically register with weaveDNS, and the probe do lo…
Browse files Browse the repository at this point in the history
…okups there.
  • Loading branch information
tomwilkie committed Feb 3, 2016
1 parent 8cf97bd commit 1f75219
Show file tree
Hide file tree
Showing 15 changed files with 930 additions and 321 deletions.
149 changes: 149 additions & 0 deletions app/weave.go
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
}
103 changes: 103 additions & 0 deletions app/weave_test.go
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
})
}
90 changes: 90 additions & 0 deletions common/backoff/backoff.go
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
}
}

}
2 changes: 2 additions & 0 deletions common/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type Cmd interface {
Start() error
Wait() error
Kill() error
Output() ([]byte, error)
Run() error
}

// Command is a hook for mocking
Expand Down
Loading

0 comments on commit 1f75219

Please sign in to comment.