mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
Merge pull request #493 from weaveworks/short-lived-internet-node-connections
In containers view, show short lived connections to/from the internet.
This commit is contained in:
26
integration/300_internet_edge_test.sh
Executable file
26
integration/300_internet_edge_test.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#! /bin/bash
|
||||
|
||||
. ./config.sh
|
||||
|
||||
start_suite "Test short lived connections from the Internet"
|
||||
|
||||
weave_on $HOST1 launch
|
||||
scope_on $HOST1 launch
|
||||
docker_on $HOST1 run -d -p 80:80 --name nginx nginx
|
||||
|
||||
do_connections() {
|
||||
while true; do
|
||||
curl -s http://$HOST1:80/ >/dev/null
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
do_connections&
|
||||
|
||||
sleep 5 # give the probe a few seconds to build a report and send it to the app
|
||||
|
||||
has_container $HOST1 nginx 1
|
||||
has_connection $HOST1 "The Internet" nginx
|
||||
|
||||
kill %do_connections
|
||||
|
||||
scope_end_suite
|
||||
@@ -31,12 +31,12 @@ weave_on() {
|
||||
DOCKER_HOST=tcp://$host:$DOCKER_PORT $WEAVE "$@"
|
||||
}
|
||||
|
||||
# this checks we have a weavescope container
|
||||
# this checks we have a named container
|
||||
has_container() {
|
||||
local host=$1
|
||||
local name=$2
|
||||
local count=$3
|
||||
assert "curl -s http://$host:4040/api/topology/containers?system=show | jq -r '[.nodes | .[] | select(.label_major == \"$name\")] | length'" $count
|
||||
assert "curl -s http://$host:4040/api/topology/containers?system=show | jq -r '[.nodes[] | select(.label_major == \"$name\")] | length'" $count
|
||||
}
|
||||
|
||||
scope_end_suite() {
|
||||
@@ -45,3 +45,19 @@ scope_end_suite() {
|
||||
docker_on $host rm -f $(docker_on $host ps -a -q) 2>/dev/null 1>&2 || true
|
||||
done
|
||||
}
|
||||
|
||||
container_id() {
|
||||
local host="$1"
|
||||
local name="$2"
|
||||
echo $(curl -s http://$host:4040/api/topology/containers?system=show | jq -r ".nodes[] | select(.label_major == \"$name\") | .id")
|
||||
}
|
||||
|
||||
# this checks we have an edge from container 1 to container 2
|
||||
has_connection() {
|
||||
local host="$1"
|
||||
local from="$2"
|
||||
local to="$3"
|
||||
local from_id=$(container_id "$host" "$from")
|
||||
local to_id=$(container_id "$host" "$to")
|
||||
assert "curl -s http://$host:4040/api/topology/containers?system=show | jq -r '.nodes[\"$from_id\"].adjacency | contains([\"$to_id\"])'" true
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ type Container interface {
|
||||
ID() string
|
||||
Image() string
|
||||
PID() int
|
||||
GetNode() report.Node
|
||||
GetNode([]net.IP) report.Node
|
||||
|
||||
StartGatheringStats() error
|
||||
StopGatheringStats()
|
||||
@@ -183,7 +183,7 @@ func (c *container) StopGatheringStats() {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *container) ports() string {
|
||||
func (c *container) ports(localAddrs []net.IP) string {
|
||||
if c.container.NetworkSettings == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -195,21 +195,27 @@ func (c *container) ports() string {
|
||||
continue
|
||||
}
|
||||
for _, b := range bindings {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s->%s", b.HostIP, b.HostPort, port))
|
||||
if b.HostIP == "0.0.0.0" {
|
||||
for _, ip := range localAddrs {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s->%s", ip, b.HostPort, port))
|
||||
}
|
||||
} else {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s->%s", b.HostIP, b.HostPort, port))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(ports, ", ")
|
||||
}
|
||||
|
||||
func (c *container) GetNode() report.Node {
|
||||
func (c *container) GetNode(localAddrs []net.IP) report.Node {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
result := report.MakeNodeWith(map[string]string{
|
||||
ContainerID: c.ID(),
|
||||
ContainerName: strings.TrimPrefix(c.container.Name, "/"),
|
||||
ContainerPorts: c.ports(),
|
||||
ContainerPorts: c.ports(localAddrs),
|
||||
ContainerCreated: c.container.Created.Format(time.RFC822),
|
||||
ContainerCommand: c.container.Path + " " + strings.Join(c.container.Args, " "),
|
||||
ImageID: c.container.Image,
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestContainer(t *testing.T) {
|
||||
"memory_usage": "12345",
|
||||
})
|
||||
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
|
||||
node := c.GetNode()
|
||||
node := c.GetNode([]net.IP{})
|
||||
for k, v := range node.Metadata {
|
||||
if v == "0" {
|
||||
delete(node.Metadata, k)
|
||||
@@ -93,7 +93,7 @@ func TestContainer(t *testing.T) {
|
||||
if c.PID() != 1 {
|
||||
t.Errorf("%s != 1", c.PID())
|
||||
}
|
||||
if !reflect.DeepEqual(docker.ExtractContainerIPs(c.GetNode()), []string{"1.2.3.4"}) {
|
||||
t.Errorf("%v != %v", docker.ExtractContainerIPs(c.GetNode()), []string{"1.2.3.4"})
|
||||
if !reflect.DeepEqual(docker.ExtractContainerIPs(c.GetNode([]net.IP{})), []string{"1.2.3.4"}) {
|
||||
t.Errorf("%v != %v", docker.ExtractContainerIPs(c.GetNode([]net.IP{})), []string{"1.2.3.4"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package docker_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
@@ -36,7 +37,7 @@ func (c *mockContainer) StartGatheringStats() error {
|
||||
|
||||
func (c *mockContainer) StopGatheringStats() {}
|
||||
|
||||
func (c *mockContainer) GetNode() report.Node {
|
||||
func (c *mockContainer) GetNode(_ []net.IP) report.Node {
|
||||
return report.MakeNodeWith(map[string]string{
|
||||
docker.ContainerID: c.c.ID,
|
||||
docker.ContainerName: c.c.Name,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
docker_client "github.com/fsouza/go-dockerclient"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
@@ -28,18 +30,23 @@ func NewReporter(registry Registry, hostID string) *Reporter {
|
||||
|
||||
// Report generates a Report containing Container and ContainerImage topologies
|
||||
func (r *Reporter) Report() (report.Report, error) {
|
||||
localAddrs, err := report.LocalAddresses()
|
||||
if err != nil {
|
||||
return report.MakeReport(), nil
|
||||
}
|
||||
|
||||
result := report.MakeReport()
|
||||
result.Container = result.Container.Merge(r.containerTopology())
|
||||
result.Container = result.Container.Merge(r.containerTopology(localAddrs))
|
||||
result.ContainerImage = result.ContainerImage.Merge(r.containerImageTopology())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Reporter) containerTopology() report.Topology {
|
||||
func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology {
|
||||
result := report.MakeTopology()
|
||||
|
||||
r.registry.WalkContainers(func(c Container) {
|
||||
nodeID := report.MakeContainerNodeID(r.hostID, c.ID())
|
||||
result.AddNode(nodeID, c.GetNode())
|
||||
result.AddNode(nodeID, c.GetNode(localAddrs))
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
@@ -3,6 +3,7 @@ package render
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -240,7 +241,8 @@ func MapHostIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
|
||||
// will be joined to containers through the process topology, and we
|
||||
// don't want to double count edges.
|
||||
func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes {
|
||||
_, ok := m.Metadata[process.PID]
|
||||
// Don't include procspied connections, to prevent double counting
|
||||
_, ok := m.Metadata[endpoint.Procspied]
|
||||
if ok {
|
||||
return RenderableNodes{}
|
||||
}
|
||||
@@ -251,9 +253,20 @@ func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes {
|
||||
if !local.Contains(net.ParseIP(addr)) {
|
||||
return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)}
|
||||
}
|
||||
return RenderableNodes{addr: NewRenderableNodeWith(addr, "", "", "", m)}
|
||||
|
||||
result := RenderableNodes{addr: NewRenderableNodeWith(addr, "", "", "", m)}
|
||||
// Emit addr:port nodes as well, so connections from the internet to containers
|
||||
// via port mapping also works.
|
||||
port, ok := m.Metadata[endpoint.Port]
|
||||
if ok {
|
||||
id := fmt.Sprintf("%s:%s", addr, port)
|
||||
result[id] = NewRenderableNodeWith(id, "", "", "", m)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var portMappingMatch = regexp.MustCompile(`([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}):([0-9]+)->([0-9]+)/tcp`)
|
||||
|
||||
// MapContainer2IP maps container nodes to their IP addresses (outputs
|
||||
// multiple nodes). This allows container to be joined directly with
|
||||
// the endpoint topology.
|
||||
@@ -264,10 +277,20 @@ func MapContainer2IP(m RenderableNode, _ report.Networks) RenderableNodes {
|
||||
return result
|
||||
}
|
||||
for _, addr := range strings.Fields(addrs) {
|
||||
n := NewRenderableNodeWith(addr, "", "", "", m)
|
||||
n.Node.Counters[containersKey] = 1
|
||||
result[addr] = n
|
||||
node := NewRenderableNodeWith(addr, "", "", "", m)
|
||||
node.Counters[containersKey] = 1
|
||||
result[addr] = node
|
||||
}
|
||||
|
||||
// also output all the host:port port mappings
|
||||
for _, mapping := range portMappingMatch.FindAllStringSubmatch(m.Metadata[docker.ContainerPorts], -1) {
|
||||
ip, port := mapping[1], mapping[2]
|
||||
id := fmt.Sprintf("%s:%s", ip, port)
|
||||
node := NewRenderableNodeWith(id, "", "", "", m)
|
||||
node.Counters[containersKey] = 1
|
||||
result[id] = node
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
94
render/short_lived_connections_test.go
Normal file
94
render/short_lived_connections_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package render_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/probe/docker"
|
||||
"github.com/weaveworks/scope/probe/endpoint"
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
"github.com/weaveworks/scope/test"
|
||||
)
|
||||
|
||||
var (
|
||||
serverHostID = "host1"
|
||||
serverHostNodeID = report.MakeHostNodeID(serverHostID)
|
||||
|
||||
randomIP = "3.4.5.6"
|
||||
randomPort = "56789"
|
||||
randomEndpointNodeID = report.MakeEndpointNodeID(serverHostID, randomIP, randomPort)
|
||||
|
||||
serverIP = "192.168.1.1"
|
||||
serverPort = "80"
|
||||
serverEndpointNodeID = report.MakeEndpointNodeID(serverHostID, serverIP, serverPort)
|
||||
|
||||
containerID = "a1b2c3d4e5"
|
||||
containerIP = "192.168.0.1"
|
||||
containerName = "foo"
|
||||
containerNodeID = report.MakeContainerNodeID(serverHostID, containerID)
|
||||
|
||||
rpt = report.Report{
|
||||
Endpoint: report.Topology{
|
||||
Nodes: report.Nodes{
|
||||
randomEndpointNodeID: report.MakeNode().WithMetadata(map[string]string{
|
||||
endpoint.Addr: randomIP,
|
||||
endpoint.Port: randomPort,
|
||||
endpoint.Conntracked: "true",
|
||||
}).WithAdjacent(serverEndpointNodeID),
|
||||
|
||||
serverEndpointNodeID: report.MakeNode().WithMetadata(map[string]string{
|
||||
endpoint.Addr: serverIP,
|
||||
endpoint.Port: serverPort,
|
||||
endpoint.Conntracked: "true",
|
||||
}),
|
||||
},
|
||||
},
|
||||
Container: report.Topology{
|
||||
Nodes: report.Nodes{
|
||||
containerNodeID: report.MakeNode().WithMetadata(map[string]string{
|
||||
docker.ContainerID: containerID,
|
||||
docker.ContainerName: containerName,
|
||||
docker.ContainerIPs: containerIP,
|
||||
docker.ContainerPorts: fmt.Sprintf("%s:%s->%s/tcp", serverIP, serverPort, serverPort),
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
}),
|
||||
},
|
||||
},
|
||||
Host: report.Topology{
|
||||
Nodes: report.Nodes{
|
||||
serverHostNodeID: report.MakeNodeWith(map[string]string{
|
||||
"local_networks": "192.168.0.0/16",
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
want = (render.RenderableNodes{
|
||||
render.TheInternetID: {
|
||||
ID: render.TheInternetID,
|
||||
LabelMajor: render.TheInternetMajor,
|
||||
Pseudo: true,
|
||||
Node: report.MakeNode().WithAdjacent(containerID),
|
||||
Origins: report.MakeIDList(randomEndpointNodeID),
|
||||
},
|
||||
containerID: {
|
||||
ID: containerID,
|
||||
LabelMajor: containerName,
|
||||
LabelMinor: serverHostID,
|
||||
Rank: "",
|
||||
Pseudo: false,
|
||||
Origins: report.MakeIDList(containerNodeID, serverEndpointNodeID, serverHostNodeID),
|
||||
Node: report.MakeNode(),
|
||||
},
|
||||
}).Prune()
|
||||
)
|
||||
|
||||
func TestShortLivedInternetNodeConnections(t *testing.T) {
|
||||
have := (render.ContainerWithImageNameRenderer.Render(rpt)).Prune()
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package report
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Networks represent a set of subnets
|
||||
@@ -29,6 +30,40 @@ func (n Networks) Contains(ip net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// LocalAddresses returns a list of the local IP addresses.
|
||||
func LocalAddresses() ([]net.IP, error) {
|
||||
result := []net.IP{}
|
||||
|
||||
infs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return []net.IP{}, err
|
||||
}
|
||||
|
||||
for _, inf := range infs {
|
||||
if strings.HasPrefix(inf.Name, "veth") ||
|
||||
strings.HasPrefix(inf.Name, "docker") ||
|
||||
strings.HasPrefix(inf.Name, "lo") {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, err := inf.Addrs()
|
||||
if err != nil {
|
||||
return []net.IP{}, err
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ipnet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, ipnet.IP)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AddLocalBridge records the subnet address associated with the bridge name
|
||||
// supplied, such that MakeAddressNodeID will scope addresses in this subnet
|
||||
// as local.
|
||||
|
||||
Reference in New Issue
Block a user