mirror of
https://github.com/weaveworks/scope.git
synced 2026-02-14 18:09:59 +00:00
Propagate network info for containers sharing network namespaces (#1401)
- Add armon/go-radix library, use this to find containers by prefix - Deal with host net namespace in the same way
This commit is contained in:
@@ -100,13 +100,15 @@ type Container interface {
|
||||
Image() string
|
||||
PID() int
|
||||
Hostname() string
|
||||
GetNode([]net.IP) report.Node
|
||||
GetNode() report.Node
|
||||
State() string
|
||||
StateString() string
|
||||
HasTTY() bool
|
||||
Container() *docker.Container
|
||||
StartGatheringStats() error
|
||||
StopGatheringStats()
|
||||
NetworkMode() (string, bool)
|
||||
NetworkInfo([]net.IP) report.Sets
|
||||
}
|
||||
|
||||
type container struct {
|
||||
@@ -284,6 +286,39 @@ func (c *container) ports(localAddrs []net.IP) report.StringSet {
|
||||
return report.MakeStringSet(ports...)
|
||||
}
|
||||
|
||||
func (c *container) NetworkMode() (string, bool) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if c.container.HostConfig != nil {
|
||||
return c.container.HostConfig.NetworkMode, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func addScopeToIPs(hostID string, ips []string) []string {
|
||||
ipsWithScopes := []string{}
|
||||
for _, ip := range ips {
|
||||
ipsWithScopes = append(ipsWithScopes, report.MakeScopedAddressNodeID(hostID, ip))
|
||||
}
|
||||
return ipsWithScopes
|
||||
}
|
||||
|
||||
func (c *container) NetworkInfo(localAddrs []net.IP) report.Sets {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
ips := c.container.NetworkSettings.SecondaryIPAddresses
|
||||
if c.container.NetworkSettings.IPAddress != "" {
|
||||
ips = append(ips, c.container.NetworkSettings.IPAddress)
|
||||
}
|
||||
// Treat all Docker IPs as local scoped.
|
||||
ipsWithScopes := addScopeToIPs(c.hostID, ips)
|
||||
return report.EmptySets.
|
||||
Add(ContainerPorts, c.ports(localAddrs)).
|
||||
Add(ContainerIPs, report.MakeStringSet(ips...)).
|
||||
Add(ContainerIPsWithScopes, report.MakeStringSet(ipsWithScopes...))
|
||||
|
||||
}
|
||||
|
||||
func (c *container) memoryUsageMetric(stats []docker.Stats) report.Metric {
|
||||
result := report.MakeMetric()
|
||||
for _, s := range stats {
|
||||
@@ -345,19 +380,9 @@ func (c *container) env() map[string]string {
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *container) GetNode(localAddrs []net.IP) report.Node {
|
||||
func (c *container) GetNode() report.Node {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
ips := c.container.NetworkSettings.SecondaryIPAddresses
|
||||
if c.container.NetworkSettings.IPAddress != "" {
|
||||
ips = append(ips, c.container.NetworkSettings.IPAddress)
|
||||
}
|
||||
// Treat all Docker IPs as local scoped.
|
||||
ipsWithScopes := []string{}
|
||||
for _, ip := range ips {
|
||||
ipsWithScopes = append(ipsWithScopes, report.MakeScopedAddressNodeID(c.hostID, ip))
|
||||
}
|
||||
|
||||
result := report.MakeNodeWith(report.MakeContainerNodeID(c.ID()), map[string]string{
|
||||
ContainerID: c.ID(),
|
||||
ContainerName: strings.TrimPrefix(c.container.Name, "/"),
|
||||
@@ -367,11 +392,7 @@ func (c *container) GetNode(localAddrs []net.IP) report.Node {
|
||||
ContainerHostname: c.Hostname(),
|
||||
ContainerState: c.StateString(),
|
||||
ContainerStateHuman: c.State(),
|
||||
}).WithSets(report.EmptySets.
|
||||
Add(ContainerPorts, c.ports(localAddrs)).
|
||||
Add(ContainerIPs, report.MakeStringSet(ips...)).
|
||||
Add(ContainerIPsWithScopes, report.MakeStringSet(ipsWithScopes...)),
|
||||
).WithMetrics(
|
||||
}).WithMetrics(
|
||||
c.metrics(),
|
||||
).WithParents(report.EmptySets.
|
||||
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID(c.Image()))),
|
||||
|
||||
@@ -74,42 +74,51 @@ func TestContainer(t *testing.T) {
|
||||
}
|
||||
|
||||
// Now see if we go them
|
||||
uptime := (now.Sub(startTime) / time.Second) * time.Second
|
||||
want := report.MakeNodeWith("ping;<container>", map[string]string{
|
||||
"docker_container_command": " ",
|
||||
"docker_container_created": "01 Jan 01 00:00 UTC",
|
||||
"docker_container_id": "ping",
|
||||
"docker_container_name": "pong",
|
||||
"docker_image_id": "baz",
|
||||
"docker_label_foo1": "bar1",
|
||||
"docker_label_foo2": "bar2",
|
||||
"docker_container_state": "running",
|
||||
"docker_container_state_human": "Up 6 years",
|
||||
"docker_container_uptime": uptime.String(),
|
||||
}).
|
||||
WithSets(report.EmptySets.
|
||||
{
|
||||
uptime := (now.Sub(startTime) / time.Second) * time.Second
|
||||
want := report.MakeNodeWith("ping;<container>", map[string]string{
|
||||
"docker_container_command": " ",
|
||||
"docker_container_created": "01 Jan 01 00:00 UTC",
|
||||
"docker_container_id": "ping",
|
||||
"docker_container_name": "pong",
|
||||
"docker_image_id": "baz",
|
||||
"docker_label_foo1": "bar1",
|
||||
"docker_label_foo2": "bar2",
|
||||
"docker_container_state": "running",
|
||||
"docker_container_state_human": "Up 6 years",
|
||||
"docker_container_uptime": uptime.String(),
|
||||
}).
|
||||
WithControls(
|
||||
docker.RestartContainer, docker.StopContainer, docker.PauseContainer,
|
||||
docker.AttachContainer, docker.ExecContainer,
|
||||
).WithMetrics(report.Metrics{
|
||||
"docker_cpu_total_usage": report.MakeMetric(),
|
||||
"docker_memory_usage": report.MakeMetric().Add(now, 12345).WithMax(45678),
|
||||
}).WithParents(report.EmptySets.
|
||||
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID("baz"))),
|
||||
)
|
||||
|
||||
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
|
||||
node := c.GetNode()
|
||||
node.Latest.ForEach(func(k, v string) {
|
||||
if v == "0" || v == "" {
|
||||
node.Latest = node.Latest.Delete(k)
|
||||
}
|
||||
})
|
||||
return node
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
want := report.EmptySets.
|
||||
Add("docker_container_ports", report.MakeStringSet("1.2.3.4:80->80/tcp", "81/tcp")).
|
||||
Add("docker_container_ips", report.MakeStringSet("1.2.3.4")).
|
||||
Add("docker_container_ips_with_scopes", report.MakeStringSet("scope;1.2.3.4")),
|
||||
).WithControls(
|
||||
docker.RestartContainer, docker.StopContainer, docker.PauseContainer,
|
||||
docker.AttachContainer, docker.ExecContainer,
|
||||
).WithMetrics(report.Metrics{
|
||||
"docker_cpu_total_usage": report.MakeMetric(),
|
||||
"docker_memory_usage": report.MakeMetric().Add(now, 12345).WithMax(45678),
|
||||
}).WithParents(report.EmptySets.
|
||||
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID("baz"))),
|
||||
)
|
||||
Add("docker_container_ips_with_scopes", report.MakeStringSet("scope;1.2.3.4"))
|
||||
|
||||
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
|
||||
node := c.GetNode([]net.IP{})
|
||||
node.Latest.ForEach(func(k, v string) {
|
||||
if v == "0" || v == "" {
|
||||
node.Latest = node.Latest.Delete(k)
|
||||
}
|
||||
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
|
||||
return c.NetworkInfo([]net.IP{})
|
||||
})
|
||||
return node
|
||||
})
|
||||
}
|
||||
|
||||
if c.Image() != "baz" {
|
||||
t.Errorf("%s != baz", c.Image())
|
||||
@@ -117,7 +126,8 @@ func TestContainer(t *testing.T) {
|
||||
if c.PID() != 2 {
|
||||
t.Errorf("%d != 2", c.PID())
|
||||
}
|
||||
if have := docker.ExtractContainerIPs(c.GetNode([]net.IP{})); !reflect.DeepEqual(have, []string{"1.2.3.4"}) {
|
||||
node := c.GetNode().WithSets(c.NetworkInfo([]net.IP{}))
|
||||
if have := docker.ExtractContainerIPs(node); !reflect.DeepEqual(have, []string{"1.2.3.4"}) {
|
||||
t.Errorf("%v != %v", have, []string{"1.2.3.4"})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/armon/go-radix"
|
||||
docker_client "github.com/fsouza/go-dockerclient"
|
||||
|
||||
"github.com/weaveworks/scope/probe/controls"
|
||||
@@ -37,6 +38,7 @@ type Registry interface {
|
||||
WalkImages(f func(*docker_client.APIImages))
|
||||
WatchContainerUpdates(ContainerUpdateWatcher)
|
||||
GetContainer(string) (Container, bool)
|
||||
GetContainerByPrefix(string) (Container, bool)
|
||||
}
|
||||
|
||||
// ContainerUpdateWatcher is the type of functions that get called when containers are updated.
|
||||
@@ -52,7 +54,7 @@ type registry struct {
|
||||
hostID string
|
||||
|
||||
watchers []ContainerUpdateWatcher
|
||||
containers map[string]Container
|
||||
containers *radix.Tree
|
||||
containersByPID map[int]Container
|
||||
images map[string]*docker_client.APIImages
|
||||
}
|
||||
@@ -88,7 +90,7 @@ func NewRegistry(interval time.Duration, pipes controls.PipeClient, collectStats
|
||||
}
|
||||
|
||||
r := ®istry{
|
||||
containers: map[string]Container{},
|
||||
containers: radix.New(),
|
||||
containersByPID: map[int]Container{},
|
||||
images: map[string]*docker_client.APIImages{},
|
||||
|
||||
@@ -186,9 +188,10 @@ func (r *registry) listenForEvents() bool {
|
||||
defer r.Unlock()
|
||||
|
||||
if r.collectStats {
|
||||
for _, c := range r.containers {
|
||||
c.StopGatheringStats()
|
||||
}
|
||||
r.containers.Walk(func(_ string, c interface{}) bool {
|
||||
c.(Container).StopGatheringStats()
|
||||
return false
|
||||
})
|
||||
}
|
||||
close(ch)
|
||||
return false
|
||||
@@ -201,12 +204,13 @@ func (r *registry) reset() {
|
||||
defer r.Unlock()
|
||||
|
||||
if r.collectStats {
|
||||
for _, c := range r.containers {
|
||||
c.StopGatheringStats()
|
||||
}
|
||||
r.containers.Walk(func(_ string, c interface{}) bool {
|
||||
c.(Container).StopGatheringStats()
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
r.containers = map[string]Container{}
|
||||
r.containers = radix.New()
|
||||
r.containersByPID = map[int]Container{}
|
||||
r.images = map[string]*docker_client.APIImages{}
|
||||
}
|
||||
@@ -270,12 +274,13 @@ func (r *registry) updateContainerState(containerID string, intendedState *strin
|
||||
}
|
||||
|
||||
// Container doesn't exist anymore, so lets stop and remove it
|
||||
container, ok := r.containers[containerID]
|
||||
c, ok := r.containers.Get(containerID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
container := c.(Container)
|
||||
|
||||
delete(r.containers, containerID)
|
||||
r.containers.Delete(containerID)
|
||||
delete(r.containersByPID, container.PID())
|
||||
if r.collectStats {
|
||||
container.StopGatheringStats()
|
||||
@@ -295,11 +300,13 @@ func (r *registry) updateContainerState(containerID string, intendedState *strin
|
||||
}
|
||||
|
||||
// Container exists, ensure we have it
|
||||
c, ok := r.containers[containerID]
|
||||
o, ok := r.containers.Get(containerID)
|
||||
var c Container
|
||||
if !ok {
|
||||
c = NewContainerStub(dockerContainer, r.hostID)
|
||||
r.containers[containerID] = c
|
||||
r.containers.Insert(containerID, c)
|
||||
} else {
|
||||
c = o.(Container)
|
||||
// potentially remove existing pid mapping.
|
||||
delete(r.containersByPID, c.PID())
|
||||
c.UpdateState(dockerContainer)
|
||||
@@ -311,9 +318,8 @@ func (r *registry) updateContainerState(containerID string, intendedState *strin
|
||||
}
|
||||
|
||||
// Trigger anyone watching for updates
|
||||
localAddrs, err := report.LocalAddresses()
|
||||
if err != nil {
|
||||
node := c.GetNode(localAddrs)
|
||||
node := c.GetNode()
|
||||
for _, f := range r.watchers {
|
||||
f(node)
|
||||
}
|
||||
@@ -350,16 +356,34 @@ func (r *registry) WalkContainers(f func(Container)) {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
|
||||
for _, container := range r.containers {
|
||||
f(container)
|
||||
}
|
||||
r.containers.Walk(func(_ string, c interface{}) bool {
|
||||
f(c.(Container))
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func (r *registry) GetContainer(id string) (Container, bool) {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
c, ok := r.containers[id]
|
||||
return c, ok
|
||||
c, ok := r.containers.Get(id)
|
||||
if ok {
|
||||
return c.(Container), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (r *registry) GetContainerByPrefix(prefix string) (Container, bool) {
|
||||
r.RLock()
|
||||
defer r.RUnlock()
|
||||
out := []interface{}{}
|
||||
r.containers.WalkPrefix(prefix, func(_ string, v interface{}) bool {
|
||||
out = append(out, v)
|
||||
return false
|
||||
})
|
||||
if len(out) == 1 {
|
||||
return out[0].(Container), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// WalkImages runs f on every image of running containers the registry
|
||||
@@ -369,10 +393,11 @@ func (r *registry) WalkImages(f func(*docker_client.APIImages)) {
|
||||
defer r.RUnlock()
|
||||
|
||||
// Loop over containers so we only emit images for running containers.
|
||||
for _, container := range r.containers {
|
||||
image, ok := r.images[container.Image()]
|
||||
r.containers.Walk(func(_ string, c interface{}) bool {
|
||||
image, ok := r.images[c.(Container).Image()]
|
||||
if ok {
|
||||
f(image)
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (c *mockContainer) StartGatheringStats() error {
|
||||
|
||||
func (c *mockContainer) StopGatheringStats() {}
|
||||
|
||||
func (c *mockContainer) GetNode(_ []net.IP) report.Node {
|
||||
func (c *mockContainer) GetNode() report.Node {
|
||||
return report.MakeNodeWith(report.MakeContainerNodeID(c.c.ID), map[string]string{
|
||||
docker.ContainerID: c.c.ID,
|
||||
docker.ContainerName: c.c.Name,
|
||||
@@ -64,6 +64,13 @@ func (c *mockContainer) GetNode(_ []net.IP) report.Node {
|
||||
)
|
||||
}
|
||||
|
||||
func (c *mockContainer) NetworkMode() (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
func (c *mockContainer) NetworkInfo([]net.IP) report.Sets {
|
||||
return report.EmptySets
|
||||
}
|
||||
|
||||
func (c *mockContainer) Container() *client.Container {
|
||||
return c.c
|
||||
}
|
||||
|
||||
@@ -49,6 +49,57 @@ var (
|
||||
ContainerImageTableTemplates = report.TableTemplates{
|
||||
ImageLabelPrefix: {ID: ImageLabelPrefix, Label: "Docker Labels", Prefix: ImageLabelPrefix},
|
||||
}
|
||||
|
||||
ContainerControls = []report.Control{
|
||||
{
|
||||
ID: AttachContainer,
|
||||
Human: "Attach",
|
||||
Icon: "fa-desktop",
|
||||
Rank: 1,
|
||||
},
|
||||
{
|
||||
ID: ExecContainer,
|
||||
Human: "Exec shell",
|
||||
Icon: "fa-terminal",
|
||||
Rank: 2,
|
||||
},
|
||||
{
|
||||
ID: StartContainer,
|
||||
Human: "Start",
|
||||
Icon: "fa-play",
|
||||
Rank: 3,
|
||||
},
|
||||
{
|
||||
ID: RestartContainer,
|
||||
Human: "Restart",
|
||||
Icon: "fa-repeat",
|
||||
Rank: 4,
|
||||
},
|
||||
{
|
||||
ID: PauseContainer,
|
||||
Human: "Pause",
|
||||
Icon: "fa-pause",
|
||||
Rank: 5,
|
||||
},
|
||||
{
|
||||
ID: UnpauseContainer,
|
||||
Human: "Unpause",
|
||||
Icon: "fa-play",
|
||||
Rank: 6,
|
||||
},
|
||||
{
|
||||
ID: StopContainer,
|
||||
Human: "Stop",
|
||||
Icon: "fa-stop",
|
||||
Rank: 7,
|
||||
},
|
||||
{
|
||||
ID: RemoveContainer,
|
||||
Human: "Remove",
|
||||
Icon: "fa-trash-o",
|
||||
Rank: 8,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Reporter generate Reports containing Container and ContainerImage topologies
|
||||
@@ -96,66 +147,73 @@ func (r *Reporter) Report() (report.Report, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func getLocalIPs() ([]string, error) {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips := []string{}
|
||||
for _, addr := range addrs {
|
||||
// Not all addrs are IPNets.
|
||||
if ipNet, ok := addr.(*net.IPNet); ok {
|
||||
ips = append(ips, ipNet.IP.String())
|
||||
}
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology {
|
||||
result := report.MakeTopology().
|
||||
WithMetadataTemplates(ContainerMetadataTemplates).
|
||||
WithMetricTemplates(ContainerMetricTemplates).
|
||||
WithTableTemplates(ContainerTableTemplates)
|
||||
result.Controls.AddControl(report.Control{
|
||||
ID: AttachContainer,
|
||||
Human: "Attach",
|
||||
Icon: "fa-desktop",
|
||||
Rank: 1,
|
||||
})
|
||||
result.Controls.AddControl(report.Control{
|
||||
ID: ExecContainer,
|
||||
Human: "Exec shell",
|
||||
Icon: "fa-terminal",
|
||||
Rank: 2,
|
||||
})
|
||||
result.Controls.AddControl(report.Control{
|
||||
ID: StartContainer,
|
||||
Human: "Start",
|
||||
Icon: "fa-play",
|
||||
Rank: 3,
|
||||
})
|
||||
result.Controls.AddControl(report.Control{
|
||||
ID: RestartContainer,
|
||||
Human: "Restart",
|
||||
Icon: "fa-repeat",
|
||||
Rank: 4,
|
||||
})
|
||||
result.Controls.AddControl(report.Control{
|
||||
ID: PauseContainer,
|
||||
Human: "Pause",
|
||||
Icon: "fa-pause",
|
||||
Rank: 5,
|
||||
})
|
||||
result.Controls.AddControl(report.Control{
|
||||
ID: UnpauseContainer,
|
||||
Human: "Unpause",
|
||||
Icon: "fa-play",
|
||||
Rank: 6,
|
||||
})
|
||||
result.Controls.AddControl(report.Control{
|
||||
ID: StopContainer,
|
||||
Human: "Stop",
|
||||
Icon: "fa-stop",
|
||||
Rank: 7,
|
||||
})
|
||||
result.Controls.AddControl(report.Control{
|
||||
ID: RemoveContainer,
|
||||
Human: "Remove",
|
||||
Icon: "fa-trash-o",
|
||||
Rank: 8,
|
||||
})
|
||||
result.Controls.AddControls(ContainerControls)
|
||||
|
||||
metadata := map[string]string{report.ControlProbeID: r.probeID}
|
||||
|
||||
nodes := []report.Node{}
|
||||
r.registry.WalkContainers(func(c Container) {
|
||||
result.AddNode(c.GetNode(localAddrs).WithLatests(metadata))
|
||||
nodes = append(nodes, c.GetNode().WithLatests(metadata))
|
||||
})
|
||||
|
||||
// Copy the IP addresses from other containers where they share network
|
||||
// namespaces & deal with containers in the host net namespace. This
|
||||
// is recursive to deal with people who decide to be clever.
|
||||
{
|
||||
hostNetworkInfo := report.EmptySets
|
||||
if hostIPs, err := getLocalIPs(); err == nil {
|
||||
hostIPsWithScopes := addScopeToIPs(r.hostID, hostIPs)
|
||||
hostNetworkInfo = hostNetworkInfo.
|
||||
Add(ContainerIPs, report.MakeStringSet(hostIPs...)).
|
||||
Add(ContainerIPsWithScopes, report.MakeStringSet(hostIPsWithScopes...))
|
||||
}
|
||||
|
||||
var networkInfo func(prefix string) report.Sets
|
||||
networkInfo = func(prefix string) report.Sets {
|
||||
container, ok := r.registry.GetContainerByPrefix(prefix)
|
||||
if !ok {
|
||||
return report.EmptySets
|
||||
}
|
||||
|
||||
networkMode, ok := container.NetworkMode()
|
||||
if ok && strings.HasPrefix(networkMode, "container:") {
|
||||
return networkInfo(networkMode[10:])
|
||||
} else if ok && networkMode == NetworkModeHost {
|
||||
return hostNetworkInfo
|
||||
}
|
||||
|
||||
return container.NetworkInfo(localAddrs)
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
id, ok := report.ParseContainerNodeID(node.ID)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
networkInfo := networkInfo(id)
|
||||
result.AddNode(node.WithSets(networkInfo))
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ func (r *mockRegistry) WatchContainerUpdates(_ docker.ContainerUpdateWatcher) {}
|
||||
|
||||
func (r *mockRegistry) GetContainer(_ string) (docker.Container, bool) { return nil, false }
|
||||
|
||||
func (r *mockRegistry) GetContainerByPrefix(_ string) (docker.Container, bool) { return nil, false }
|
||||
|
||||
var (
|
||||
mockRegistryInstance = &mockRegistry{
|
||||
containersByPID: map[int]docker.Container{
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/weaveworks/scope/probe/docker"
|
||||
"github.com/weaveworks/scope/probe/endpoint"
|
||||
"github.com/weaveworks/scope/probe/host"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
@@ -67,48 +66,6 @@ var ContainerRenderer = MakeFilter(
|
||||
),
|
||||
)
|
||||
|
||||
type containerWithHostIPsRenderer struct {
|
||||
Renderer
|
||||
}
|
||||
|
||||
// Render produces a process graph where the ips for host network mode are set
|
||||
// to the host's IPs.
|
||||
func (r containerWithHostIPsRenderer) Render(rpt report.Report, dct Decorator) report.Nodes {
|
||||
containers := r.Renderer.Render(rpt, dct)
|
||||
hosts := SelectHost.Render(rpt, dct)
|
||||
|
||||
outputs := report.Nodes{}
|
||||
for id, c := range containers {
|
||||
outputs[id] = c
|
||||
networkMode, ok := c.Latest.Lookup(docker.ContainerNetworkMode)
|
||||
if !ok || networkMode != docker.NetworkModeHost {
|
||||
continue
|
||||
}
|
||||
|
||||
h, ok := hosts[report.MakeHostNodeID(report.ExtractHostID(c))]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
newIPs := report.MakeStringSet()
|
||||
hostNetworks, _ := h.Sets.Lookup(host.LocalNetworks)
|
||||
for _, cidr := range hostNetworks {
|
||||
if ip, _, err := net.ParseCIDR(cidr); err == nil {
|
||||
newIPs = newIPs.Add(ip.String())
|
||||
}
|
||||
}
|
||||
|
||||
output := c.Copy()
|
||||
output.Sets = c.Sets.Add(docker.ContainerIPs, newIPs)
|
||||
outputs[id] = output
|
||||
}
|
||||
return outputs
|
||||
}
|
||||
|
||||
// ContainerWithHostIPsRenderer is a Renderer which produces a container graph
|
||||
// enriched with host IPs on containers where NetworkMode is Host
|
||||
var ContainerWithHostIPsRenderer = containerWithHostIPsRenderer{ContainerRenderer}
|
||||
|
||||
type containerWithImageNameRenderer struct {
|
||||
Renderer
|
||||
}
|
||||
@@ -140,7 +97,7 @@ func (r containerWithImageNameRenderer) Render(rpt report.Report, dct Decorator)
|
||||
|
||||
// ContainerWithImageNameRenderer is a Renderer which produces a container
|
||||
// graph where the ranks are the image names, not their IDs
|
||||
var ContainerWithImageNameRenderer = ApplyDecorators(containerWithImageNameRenderer{ContainerWithHostIPsRenderer})
|
||||
var ContainerWithImageNameRenderer = ApplyDecorators(containerWithImageNameRenderer{ContainerRenderer})
|
||||
|
||||
// ContainerImageRenderer is a Renderer which produces a renderable container
|
||||
// image graph by merging the container graph and the container image topology.
|
||||
|
||||
@@ -69,28 +69,6 @@ func TestContainerFilterRenderer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerWithHostIPsRenderer(t *testing.T) {
|
||||
input := fixture.Report.Copy()
|
||||
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
|
||||
docker.ContainerNetworkMode: "host",
|
||||
})
|
||||
nodes := render.ContainerWithHostIPsRenderer.Render(input, render.FilterNoop)
|
||||
|
||||
// Test host network nodes get the host IPs added.
|
||||
haveNode, ok := nodes[fixture.ClientContainerNodeID]
|
||||
if !ok {
|
||||
t.Fatal("Expected output to have the client container node")
|
||||
}
|
||||
have, ok := haveNode.Sets.Lookup(docker.ContainerIPs)
|
||||
if !ok {
|
||||
t.Fatal("Container had no IPs set.")
|
||||
}
|
||||
want := report.MakeStringSet("10.10.10.0")
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerHostnameRenderer(t *testing.T) {
|
||||
have := Prune(render.ContainerHostnameRenderer.Render(fixture.Report, render.FilterNoop))
|
||||
want := Prune(expected.RenderedContainerHostnames)
|
||||
|
||||
@@ -37,11 +37,18 @@ func (cs Controls) Copy() Controls {
|
||||
return result
|
||||
}
|
||||
|
||||
// AddControl returns a fresh Controls, c added to cs.
|
||||
// AddControl adds c added to cs.
|
||||
func (cs Controls) AddControl(c Control) {
|
||||
cs[c.ID] = c
|
||||
}
|
||||
|
||||
// AddControls adds a collection of controls to cs.
|
||||
func (cs Controls) AddControls(controls []Control) {
|
||||
for _, c := range controls {
|
||||
cs[c.ID] = c
|
||||
}
|
||||
}
|
||||
|
||||
// NodeControls represent the individual controls that are valid for a given
|
||||
// node at a given point in time. Its is immutable. A zero-value for Timestamp
|
||||
// indicated this NodeControls is 'not set'.
|
||||
|
||||
20
vendor/github.com/armon/go-radix/LICENSE
generated
vendored
Normal file
20
vendor/github.com/armon/go-radix/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Armon Dadgar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
496
vendor/github.com/armon/go-radix/radix.go
generated
vendored
Normal file
496
vendor/github.com/armon/go-radix/radix.go
generated
vendored
Normal file
@@ -0,0 +1,496 @@
|
||||
package radix
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WalkFn is used when walking the tree. Takes a
|
||||
// key and value, returning if iteration should
|
||||
// be terminated.
|
||||
type WalkFn func(s string, v interface{}) bool
|
||||
|
||||
// leafNode is used to represent a value
|
||||
type leafNode struct {
|
||||
key string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
// edge is used to represent an edge node
|
||||
type edge struct {
|
||||
label byte
|
||||
node *node
|
||||
}
|
||||
|
||||
type node struct {
|
||||
// leaf is used to store possible leaf
|
||||
leaf *leafNode
|
||||
|
||||
// prefix is the common prefix we ignore
|
||||
prefix string
|
||||
|
||||
// Edges should be stored in-order for iteration.
|
||||
// We avoid a fully materialized slice to save memory,
|
||||
// since in most cases we expect to be sparse
|
||||
edges edges
|
||||
}
|
||||
|
||||
func (n *node) isLeaf() bool {
|
||||
return n.leaf != nil
|
||||
}
|
||||
|
||||
func (n *node) addEdge(e edge) {
|
||||
n.edges = append(n.edges, e)
|
||||
n.edges.Sort()
|
||||
}
|
||||
|
||||
func (n *node) replaceEdge(e edge) {
|
||||
num := len(n.edges)
|
||||
idx := sort.Search(num, func(i int) bool {
|
||||
return n.edges[i].label >= e.label
|
||||
})
|
||||
if idx < num && n.edges[idx].label == e.label {
|
||||
n.edges[idx].node = e.node
|
||||
return
|
||||
}
|
||||
panic("replacing missing edge")
|
||||
}
|
||||
|
||||
func (n *node) getEdge(label byte) *node {
|
||||
num := len(n.edges)
|
||||
idx := sort.Search(num, func(i int) bool {
|
||||
return n.edges[i].label >= label
|
||||
})
|
||||
if idx < num && n.edges[idx].label == label {
|
||||
return n.edges[idx].node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) delEdge(label byte) {
|
||||
num := len(n.edges)
|
||||
idx := sort.Search(num, func(i int) bool {
|
||||
return n.edges[i].label >= label
|
||||
})
|
||||
if idx < num && n.edges[idx].label == label {
|
||||
copy(n.edges[idx:], n.edges[idx+1:])
|
||||
n.edges[len(n.edges)-1] = edge{}
|
||||
n.edges = n.edges[:len(n.edges)-1]
|
||||
}
|
||||
}
|
||||
|
||||
type edges []edge
|
||||
|
||||
func (e edges) Len() int {
|
||||
return len(e)
|
||||
}
|
||||
|
||||
func (e edges) Less(i, j int) bool {
|
||||
return e[i].label < e[j].label
|
||||
}
|
||||
|
||||
func (e edges) Swap(i, j int) {
|
||||
e[i], e[j] = e[j], e[i]
|
||||
}
|
||||
|
||||
func (e edges) Sort() {
|
||||
sort.Sort(e)
|
||||
}
|
||||
|
||||
// Tree implements a radix tree. This can be treated as a
|
||||
// Dictionary abstract data type. The main advantage over
|
||||
// a standard hash map is prefix-based lookups and
|
||||
// ordered iteration,
|
||||
type Tree struct {
|
||||
root *node
|
||||
size int
|
||||
}
|
||||
|
||||
// New returns an empty Tree
|
||||
func New() *Tree {
|
||||
return NewFromMap(nil)
|
||||
}
|
||||
|
||||
// NewFromMap returns a new tree containing the keys
|
||||
// from an existing map
|
||||
func NewFromMap(m map[string]interface{}) *Tree {
|
||||
t := &Tree{root: &node{}}
|
||||
for k, v := range m {
|
||||
t.Insert(k, v)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Len is used to return the number of elements in the tree
|
||||
func (t *Tree) Len() int {
|
||||
return t.size
|
||||
}
|
||||
|
||||
// longestPrefix finds the length of the shared prefix
|
||||
// of two strings
|
||||
func longestPrefix(k1, k2 string) int {
|
||||
max := len(k1)
|
||||
if l := len(k2); l < max {
|
||||
max = l
|
||||
}
|
||||
var i int
|
||||
for i = 0; i < max; i++ {
|
||||
if k1[i] != k2[i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// Insert is used to add a newentry or update
|
||||
// an existing entry. Returns if updated.
|
||||
func (t *Tree) Insert(s string, v interface{}) (interface{}, bool) {
|
||||
var parent *node
|
||||
n := t.root
|
||||
search := s
|
||||
for {
|
||||
// Handle key exhaution
|
||||
if len(search) == 0 {
|
||||
if n.isLeaf() {
|
||||
old := n.leaf.val
|
||||
n.leaf.val = v
|
||||
return old, true
|
||||
}
|
||||
|
||||
n.leaf = &leafNode{
|
||||
key: s,
|
||||
val: v,
|
||||
}
|
||||
t.size++
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Look for the edge
|
||||
parent = n
|
||||
n = n.getEdge(search[0])
|
||||
|
||||
// No edge, create one
|
||||
if n == nil {
|
||||
e := edge{
|
||||
label: search[0],
|
||||
node: &node{
|
||||
leaf: &leafNode{
|
||||
key: s,
|
||||
val: v,
|
||||
},
|
||||
prefix: search,
|
||||
},
|
||||
}
|
||||
parent.addEdge(e)
|
||||
t.size++
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Determine longest prefix of the search key on match
|
||||
commonPrefix := longestPrefix(search, n.prefix)
|
||||
if commonPrefix == len(n.prefix) {
|
||||
search = search[commonPrefix:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Split the node
|
||||
t.size++
|
||||
child := &node{
|
||||
prefix: search[:commonPrefix],
|
||||
}
|
||||
parent.replaceEdge(edge{
|
||||
label: search[0],
|
||||
node: child,
|
||||
})
|
||||
|
||||
// Restore the existing node
|
||||
child.addEdge(edge{
|
||||
label: n.prefix[commonPrefix],
|
||||
node: n,
|
||||
})
|
||||
n.prefix = n.prefix[commonPrefix:]
|
||||
|
||||
// Create a new leaf node
|
||||
leaf := &leafNode{
|
||||
key: s,
|
||||
val: v,
|
||||
}
|
||||
|
||||
// If the new key is a subset, add to to this node
|
||||
search = search[commonPrefix:]
|
||||
if len(search) == 0 {
|
||||
child.leaf = leaf
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Create a new edge for the node
|
||||
child.addEdge(edge{
|
||||
label: search[0],
|
||||
node: &node{
|
||||
leaf: leaf,
|
||||
prefix: search,
|
||||
},
|
||||
})
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// Delete is used to delete a key, returning the previous
|
||||
// value and if it was deleted
|
||||
func (t *Tree) Delete(s string) (interface{}, bool) {
|
||||
var parent *node
|
||||
var label byte
|
||||
n := t.root
|
||||
search := s
|
||||
for {
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
if !n.isLeaf() {
|
||||
break
|
||||
}
|
||||
goto DELETE
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
parent = n
|
||||
label = search[0]
|
||||
n = n.getEdge(label)
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
|
||||
DELETE:
|
||||
// Delete the leaf
|
||||
leaf := n.leaf
|
||||
n.leaf = nil
|
||||
t.size--
|
||||
|
||||
// Check if we should delete this node from the parent
|
||||
if parent != nil && len(n.edges) == 0 {
|
||||
parent.delEdge(label)
|
||||
}
|
||||
|
||||
// Check if we should merge this node
|
||||
if n != t.root && len(n.edges) == 1 {
|
||||
n.mergeChild()
|
||||
}
|
||||
|
||||
// Check if we should merge the parent's other child
|
||||
if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() {
|
||||
parent.mergeChild()
|
||||
}
|
||||
|
||||
return leaf.val, true
|
||||
}
|
||||
|
||||
func (n *node) mergeChild() {
|
||||
e := n.edges[0]
|
||||
child := e.node
|
||||
n.prefix = n.prefix + child.prefix
|
||||
n.leaf = child.leaf
|
||||
n.edges = child.edges
|
||||
}
|
||||
|
||||
// Get is used to lookup a specific key, returning
|
||||
// the value and if it was found
|
||||
func (t *Tree) Get(s string) (interface{}, bool) {
|
||||
n := t.root
|
||||
search := s
|
||||
for {
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
if n.isLeaf() {
|
||||
return n.leaf.val, true
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
n = n.getEdge(search[0])
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// LongestPrefix is like Get, but instead of an
|
||||
// exact match, it will return the longest prefix match.
|
||||
func (t *Tree) LongestPrefix(s string) (string, interface{}, bool) {
|
||||
var last *leafNode
|
||||
n := t.root
|
||||
search := s
|
||||
for {
|
||||
// Look for a leaf node
|
||||
if n.isLeaf() {
|
||||
last = n.leaf
|
||||
}
|
||||
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
n = n.getEdge(search[0])
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if last != nil {
|
||||
return last.key, last.val, true
|
||||
}
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// Minimum is used to return the minimum value in the tree
|
||||
func (t *Tree) Minimum() (string, interface{}, bool) {
|
||||
n := t.root
|
||||
for {
|
||||
if n.isLeaf() {
|
||||
return n.leaf.key, n.leaf.val, true
|
||||
}
|
||||
if len(n.edges) > 0 {
|
||||
n = n.edges[0].node
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// Maximum is used to return the maximum value in the tree
|
||||
func (t *Tree) Maximum() (string, interface{}, bool) {
|
||||
n := t.root
|
||||
for {
|
||||
if num := len(n.edges); num > 0 {
|
||||
n = n.edges[num-1].node
|
||||
continue
|
||||
}
|
||||
if n.isLeaf() {
|
||||
return n.leaf.key, n.leaf.val, true
|
||||
}
|
||||
break
|
||||
}
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// Walk is used to walk the tree
|
||||
func (t *Tree) Walk(fn WalkFn) {
|
||||
recursiveWalk(t.root, fn)
|
||||
}
|
||||
|
||||
// WalkPrefix is used to walk the tree under a prefix
|
||||
func (t *Tree) WalkPrefix(prefix string, fn WalkFn) {
|
||||
n := t.root
|
||||
search := prefix
|
||||
for {
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
recursiveWalk(n, fn)
|
||||
return
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
n = n.getEdge(search[0])
|
||||
if n == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
|
||||
} else if strings.HasPrefix(n.prefix, search) {
|
||||
// Child may be under our search prefix
|
||||
recursiveWalk(n, fn)
|
||||
return
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// WalkPath is used to walk the tree, but only visiting nodes
|
||||
// from the root down to a given leaf. Where WalkPrefix walks
|
||||
// all the entries *under* the given prefix, this walks the
|
||||
// entries *above* the given prefix.
|
||||
func (t *Tree) WalkPath(path string, fn WalkFn) {
|
||||
n := t.root
|
||||
search := path
|
||||
for {
|
||||
// Visit the leaf values if any
|
||||
if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check for key exhaution
|
||||
if len(search) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Look for an edge
|
||||
n = n.getEdge(search[0])
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Consume the search prefix
|
||||
if strings.HasPrefix(search, n.prefix) {
|
||||
search = search[len(n.prefix):]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recursiveWalk is used to do a pre-order walk of a node
|
||||
// recursively. Returns true if the walk should be aborted
|
||||
func recursiveWalk(n *node, fn WalkFn) bool {
|
||||
// Visit the leaf values if any
|
||||
if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Recurse on the children
|
||||
for _, e := range n.edges {
|
||||
if recursiveWalk(e.node, fn) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ToMap is used to walk the tree and convert it into a map
|
||||
func (t *Tree) ToMap() map[string]interface{} {
|
||||
out := make(map[string]interface{}, t.size)
|
||||
t.Walk(func(k string, v interface{}) bool {
|
||||
out[k] = v
|
||||
return false
|
||||
})
|
||||
return out
|
||||
}
|
||||
157
vendor/manifest
vendored
157
vendor/manifest
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user