Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch container IPs directly from the namespace instead of calling 'weave ps' #3207

Merged
merged 6 commits into from
Jun 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.9.2-stretch
FROM golang:1.10.2-stretch
ENV SCOPE_SKIP_UI_ASSETS true
RUN apt-get update && \
apt-get install -y libpcap-dev python-requests time file shellcheck git gcc-arm-linux-gnueabihf curl build-essential python-pip && \
Expand Down
57 changes: 1 addition & 56 deletions common/weave/client.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package weave

import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
Expand All @@ -24,8 +22,7 @@ const dockerAPIVersion = "1.22" // Support Docker Engine >= 1.10
type Client interface {
Status() (Status, error)
AddDNSEntry(fqdn, containerid string, ip net.IP) error
PS() (map[string]PSEntry, error) // on the interface for mocking
Expose() error // on the interface for mocking
Expose() error // on the interface for mocking
}

// Status describes whats happen in the Weave Net router.
Expand Down Expand Up @@ -107,16 +104,8 @@ type Plugin struct {
DriverName string
}

var weavePsMatch = regexp.MustCompile(`^([0-9a-f]{12}) ((?:[0-9a-f][0-9a-f]\:){5}(?:[0-9a-f][0-9a-f]))(.*)$`)
var ipMatch = regexp.MustCompile(`([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})(/[0-9]+)`)

// PSEntry is a row from the output of `weave ps`
type PSEntry struct {
ContainerIDPrefix string
MACAddress string
IPs []string
}

type client struct {
url string
}
Expand Down Expand Up @@ -176,50 +165,6 @@ func (c *client) AddDNSEntry(fqdn, containerID string, ip net.IP) error {
return nil
}

func (c *client) PS() (map[string]PSEntry, error) {
cmd := weaveCommand("--local", "ps")
stdOut, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
stdErr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}

psEntriesByPrefix := map[string]PSEntry{}
scanner := bufio.NewScanner(stdOut)
for scanner.Scan() {
line := scanner.Text()
groups := weavePsMatch.FindStringSubmatch(line)
if len(groups) == 0 {
continue
}
containerIDPrefix, macAddress, ips := groups[1], groups[2], []string{}
for _, ipGroup := range ipMatch.FindAllStringSubmatch(groups[3], -1) {
ips = append(ips, ipGroup[1])
}
psEntriesByPrefix[containerIDPrefix] = PSEntry{
ContainerIDPrefix: containerIDPrefix,
MACAddress: macAddress,
IPs: ips,
}
}
scannerErr := scanner.Err()
slurp, _ := ioutil.ReadAll(stdErr)
cmdErr := cmd.Wait()
if cmdErr != nil {
return nil, errorf("%s: %q", cmdErr, slurp)
}
if scannerErr != nil {
return nil, scannerErr
}
return psEntriesByPrefix, nil
}

func (c *client) Expose() error {
cmd := weaveCommand("--local", "ps", "weave:expose")
output, err := cmd.Output()
Expand Down
25 changes: 0 additions & 25 deletions common/weave/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,31 +139,6 @@ func TestDNSAdd(t *testing.T) {
}
}

func TestPS(t *testing.T) {
oldExecCmd := exec.Command
defer func() { exec.Command = oldExecCmd }()
exec.Command = func(name string, args ...string) exec.Cmd {
return testExec.NewMockCmdString(fmt.Sprintf("%s %s %s/24\n", mockContainerID, mockContainerMAC, mockContainerIP))
}

client := weave.NewClient("")
entries, err := client.PS()
if err != nil {
t.Fatal(err)
}

want := map[string]weave.PSEntry{
mockContainerID: {
ContainerIDPrefix: mockContainerID,
MACAddress: mockContainerMAC,
IPs: []string{mockContainerIP},
},
}
if !reflect.DeepEqual(entries, want) {
t.Fatal(test.Diff(entries, want))
}
}

func TestExpose(t *testing.T) {
oldExecCmd := exec.Command
defer func() { exec.Command = oldExecCmd }()
Expand Down
11 changes: 11 additions & 0 deletions probe/docker/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,17 @@ func (c *container) NetworkInfo(localAddrs []net.IP) report.Sets {
ips = append(ips, c.container.NetworkSettings.IPAddress)
}

// Fetch IP addresses from the container's namespace
cidrs, err := namespaceIPAddresses(c.container.State.Pid)
if err != nil {
log.Debugf("container %s: failed to get addresses: %s", c.container.ID, err)
}
for _, cidr := range cidrs {
// This address can duplicate an address fetched from Docker earlier,
// but we eventually turn the lists into sets which will remove duplicates.
ips = append(ips, cidr.IP.String())
}

// For now, for the proof-of-concept, we just add networks as a set of
// names. For the next iteration, we will probably want to create a new
// Network topology, populate the network nodes with all of the details
Expand Down
85 changes: 85 additions & 0 deletions probe/docker/network_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// withNetNS function requires a fix that first appeared in Go version 1.10
// +build go1.10

package docker

import (
"fmt"
"net"
"runtime"

"github.com/vishvananda/netlink"
"github.com/vishvananda/netns"
"golang.org/x/sys/unix"
)

// Code adapted from github.com/weaveworks/weave/net/netdev.go

// Return any non-local IP addresses for processID if in a non-root namespace
func namespaceIPAddresses(processID int) ([]*net.IPNet, error) {
// Ignore if this process is running in the root namespace
netnsRoot, err := netns.GetFromPid(1)
if err != nil {
return nil, fmt.Errorf("unable to open root namespace: %s", err)
}
defer netnsRoot.Close()
netnsContainer, err := netns.GetFromPid(processID)
if err != nil {
return nil, err
}
defer netnsContainer.Close()
if netnsRoot.Equal(netnsContainer) {
return nil, nil
}

var cidrs []*net.IPNet
err = withNetNS(netnsContainer, func() error {
cidrs, err = allNonLocalAddresses()
return err
})

return cidrs, err
}

// return all non-local IP addresses from the current namespace
func allNonLocalAddresses() ([]*net.IPNet, error) {
var cidrs []*net.IPNet
links, err := netlink.LinkList()
if err != nil {
return nil, err
}

for _, link := range links {
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return nil, err
}
for _, addr := range addrs {
// Exclude link-local ipv6 addresses, localhost, etc. Hope this is the correct test.
if addr.Scope == unix.RT_SCOPE_UNIVERSE {
cidrs = append(cidrs, addr.IPNet)
}
}
}
return cidrs, nil
}

// Run the 'work' function in a different network namespace
func withNetNS(ns netns.NsHandle, work func() error) error {
runtime.LockOSThread()
defer runtime.UnlockOSThread()

oldNs, err := netns.Get()
if err == nil {
defer oldNs.Close()

err = netns.Set(ns)
if err == nil {
defer netns.Set(oldNs)

err = work()
}
}

return err
}
12 changes: 12 additions & 0 deletions probe/docker/network_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// +build !linux

package docker

import (
"errors"
"net"
)

func namespaceIPAddresses(processID int) ([]*net.IPNet, error) {
return nil, errors.New("namespaceIPAddresses not implemented on this platform")
}
41 changes: 3 additions & 38 deletions probe/overlay/weave.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ type Weave struct {

mtx sync.RWMutex
statusCache weave.Status
psCache map[string]weave.PSEntry

backoff backoff.Interface
psBackoff backoff.Interface
Expand All @@ -160,43 +159,23 @@ type Weave struct {
// address. The address should be an IP or FQDN, no port.
func NewWeave(hostID string, client weave.Client) (*Weave, error) {
w := &Weave{
client: client,
hostID: hostID,
psCache: map[string]weave.PSEntry{},
client: client,
hostID: hostID,
}

w.backoff = backoff.New(w.status, "collecting weave status")
w.backoff.SetInitialBackoff(5 * time.Second)
go w.backoff.Start()

w.psBackoff = backoff.New(w.ps, "collecting weave ps")
w.psBackoff.SetInitialBackoff(10 * time.Second)
go w.psBackoff.Start()

return w, nil
}

// Name of this reporter/tagger/ticker, for metrics gathering
func (*Weave) Name() string { return "Weave" }

// Stop gathering weave ps output.
// Stop gathering weave status.
func (w *Weave) Stop() {
w.backoff.Stop()
w.psBackoff.Stop()
}

func (w *Weave) ps() (bool, error) {
psEntriesByPrefix, err := w.client.PS()

w.mtx.Lock()
defer w.mtx.Unlock()

if err != nil {
w.psCache = map[string]weave.PSEntry{}
} else {
w.psCache = psEntriesByPrefix
}
return false, err
}

func (w *Weave) status() (bool, error) {
Expand Down Expand Up @@ -246,20 +225,6 @@ func (w *Weave) Tag(r report.Report) (report.Report, error) {
if len(prefix) > maxPrefixSize {
prefix = prefix[:maxPrefixSize]
}
entry, ok := w.psCache[prefix]
if !ok {
continue
}

ipsWithScope := report.MakeStringSet()
for _, ip := range entry.IPs {
ipsWithScope = ipsWithScope.Add(report.MakeAddressNodeID("", ip))
}
node = node.WithSet(docker.ContainerIPs, report.MakeStringSet(entry.IPs...))
node = node.WithSet(docker.ContainerIPsWithScopes, ipsWithScope)
node = node.WithLatests(map[string]string{
WeaveMACAddress: entry.MACAddress,
})
r.Container.Nodes[id] = node
}
return r, nil
Expand Down
15 changes: 1 addition & 14 deletions probe/overlay/weave_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ import (
)

const (
mockHostID = "host1"
mockContainerIPWithScope = ";" + weave.MockContainerIP
mockHostID = "host1"
)

func runTest(t *testing.T, f func(*overlay.Weave)) {
Expand Down Expand Up @@ -59,18 +58,6 @@ func TestContainerTopologyTagging(t *testing.T) {
if have, ok := node.Latest.Lookup(overlay.WeaveDNSHostname); !ok || have != weave.MockHostname {
t.Errorf("Expected weave dns hostname %q, got %q", weave.MockHostname, have)
}
// Should have Weave MAC Address
if have, ok := node.Latest.Lookup(overlay.WeaveMACAddress); !ok || have != weave.MockContainerMAC {
t.Errorf("Expected weave mac address %q, got %q", weave.MockContainerMAC, have)
}
// Should have Weave container ip
if have, ok := node.Sets.Lookup(docker.ContainerIPs); !ok || !have.Contains(weave.MockContainerIP) {
t.Errorf("Expected container ips to include the weave IP %q, got %q", weave.MockContainerIP, have)
}
// Should have Weave container ip (with scope)
if have, ok := node.Sets.Lookup(docker.ContainerIPsWithScopes); !ok || !have.Contains(mockContainerIPWithScope) {
t.Errorf("Expected container ips to include the weave IP (with scope) %q, got %q", mockContainerIPWithScope, have)
}
}

runTest(t, test)
Expand Down
13 changes: 0 additions & 13 deletions test/weave/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ const (
MockWeavePeerNickName = "winny"
MockWeaveDefaultSubnet = "10.32.0.1/12"
MockContainerID = "83183a667c01"
MockContainerMAC = "d6:f2:5a:12:36:a8"
MockContainerIP = "10.0.0.123"
MockHostname = "hostname.weave.local"
MockProxyAddress = "unix:///foo/bar/weave.sock"
MockDriverName = "weave_mock"
Expand Down Expand Up @@ -70,17 +68,6 @@ func (MockClient) AddDNSEntry(fqdn, containerid string, ip net.IP) error {
return nil
}

// PS implements weave.Client
func (MockClient) PS() (map[string]weave.PSEntry, error) {
return map[string]weave.PSEntry{
MockContainerID: {
ContainerIDPrefix: MockContainerID,
MACAddress: MockContainerMAC,
IPs: []string{MockContainerIP},
},
}, nil
}

// Expose implements weave.Client
func (MockClient) Expose() error {
return nil
Expand Down
Loading