Filter system containers from topologies; add API to control filters.

This commit is contained in:
Peter Bourgon
2015-08-31 13:52:51 +02:00
committed by Tom Wilkie
parent 6a6622e398
commit 1dfc725706
8 changed files with 2572 additions and 2259 deletions

View File

@@ -1,6 +1,9 @@
package render
import (
"strings"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/report"
)
@@ -124,7 +127,8 @@ func (m Map) EdgeMetadata(rpt report.Report, srcRenderableID, dstRenderableID st
}
// CustomRenderer allow for mapping functions that recived the entire topology
// in one call - useful for functions that need to consider the entire graph
// in one call - useful for functions that need to consider the entire graph.
// We should minimise the use of this renderer type, as it is very inflexible.
type CustomRenderer struct {
RenderFunc func(RenderableNodes) RenderableNodes
Renderer
@@ -135,68 +139,116 @@ func (c CustomRenderer) Render(rpt report.Report) RenderableNodes {
return c.RenderFunc(c.Renderer.Render(rpt))
}
// IsConnected is the key added to Node.Metadata by ColorConnected
// to indicate a node has an edge pointing to it or from it
const IsConnected = "is_connected"
// OnlyConnected filters out unconnected RenderedNodes
func OnlyConnected(input RenderableNodes) RenderableNodes {
output := RenderableNodes{}
for id, node := range ColorConnected(input) {
if _, ok := node.Node.Metadata[IsConnected]; ok {
output[id] = node
}
}
return output
}
// FilterUnconnected produces a renderer that filters unconnected nodes
// from the given renderer
func FilterUnconnected(r Renderer) Renderer {
return CustomRenderer{
RenderFunc: OnlyConnected,
Renderer: r,
}
}
// ColorConnected colors nodes with the IsConnected key if
// they have edges to or from them.
func ColorConnected(input RenderableNodes) RenderableNodes {
connected := map[string]struct{}{}
void := struct{}{}
func ColorConnected(r Renderer) Renderer {
return CustomRenderer{
Renderer: r,
RenderFunc: func(input RenderableNodes) RenderableNodes {
connected := map[string]struct{}{}
void := struct{}{}
for id, node := range input {
if len(node.Adjacency) == 0 {
continue
}
for id, node := range input {
if len(node.Adjacency) == 0 {
continue
}
connected[id] = void
for _, id := range node.Adjacency {
connected[id] = void
}
connected[id] = void
for _, id := range node.Adjacency {
connected[id] = void
}
}
for id := range connected {
node := input[id]
node.Metadata[IsConnected] = "true"
input[id] = node
}
return input
},
}
for id := range connected {
node := input[id]
node.Node.Metadata[IsConnected] = "true"
input[id] = node
}
return input
}
// Filter removes nodes from a view based on a predicate.
type Filter struct {
Renderer
f func(RenderableNode) bool
FilterFunc func(RenderableNode) bool
}
// Render implements Renderer
func (f Filter) Render(rpt report.Report) RenderableNodes {
output := RenderableNodes{}
for id, node := range f.Renderer.Render(rpt) {
if f.f(node) {
if f.FilterFunc(node) {
output[id] = node
}
}
// Deleted nodes also need to be cut as destinations in adjacency lists.
for id, node := range output {
newAdjacency := make(report.IDList, 0, len(node.Adjacency))
for _, dstID := range node.Adjacency {
if _, ok := output[dstID]; ok {
newAdjacency = newAdjacency.Add(dstID)
}
}
node.Adjacency = newAdjacency
output[id] = node
}
return output
}
// IsConnected is the key added to Node.Metadata by ColorConnected
// to indicate a node has an edge pointing to it or from it
const IsConnected = "is_connected"
// FilterUnconnected produces a renderer that filters unconnected nodes
// from the given renderer
func FilterUnconnected(r Renderer) Renderer {
return Filter{
Renderer: ColorConnected(r),
FilterFunc: func(node RenderableNode) bool {
_, ok := node.Metadata[IsConnected]
return ok
},
}
}
// FilterSystem is a Renderer which filters out system nodes.
func FilterSystem(r Renderer) Renderer {
return Filter{
Renderer: r,
FilterFunc: func(node RenderableNode) bool {
containerName := node.Metadata[docker.ContainerName]
if _, ok := systemContainerNames[containerName]; ok {
return false
}
imagePrefix := strings.SplitN(node.Metadata[docker.ImageName], ":", 2)[0] // :(
if _, ok := systemImagePrefixes[imagePrefix]; ok {
return false
}
if node.Metadata[docker.LabelPrefix+"works.weave.role"] == "system" {
return false
}
return true
},
}
}
var systemContainerNames = map[string]struct{}{
"weavescope": {},
"weavedns": {},
"weave": {},
"weaveproxy": {},
"weaveexec": {},
"ecs-agent": {},
}
var systemImagePrefixes = map[string]struct{}{
"weaveworks/scope": {},
"weaveworks/weavedns": {},
"weaveworks/weave": {},
"weaveworks/weaveproxy": {},
"weaveworks/weaveexec": {},
"amazon/amazon-ecs-agent": {},
}

View File

@@ -187,7 +187,29 @@ func TestFilterRender(t *testing.T) {
}
have := expected.Sterilize(renderer.Render(report.MakeReport()))
if !reflect.DeepEqual(want, have) {
t.Errorf("want %+v, have %+v", want, have)
t.Error(test.Diff(want, have))
}
}
func TestFilterRender2(t *testing.T) {
// Test adjacencies are removed for filtered nodes.
renderer := render.Filter{
FilterFunc: func(node render.RenderableNode) bool {
return node.ID != "bar"
},
Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
"foo": {ID: "foo", Node: report.MakeNode().WithAdjacent("bar")},
"bar": {ID: "bar", Node: report.MakeNode().WithAdjacent("foo")},
"baz": {ID: "baz", Node: report.MakeNode()},
}},
}
want := render.RenderableNodes{
"foo": {ID: "foo", Node: report.MakeNode()},
"baz": {ID: "baz", Node: report.MakeNode()},
}
have := expected.Sterilize(renderer.Render(report.MakeReport()))
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}

View File

@@ -125,6 +125,15 @@ func (rn RenderableNode) Copy() RenderableNode {
// RenderableNodes is a set of RenderableNodes
type RenderableNodes map[string]RenderableNode
// Copy produces a deep copy of the RenderableNodes
func (rns RenderableNodes) Copy() RenderableNodes {
result := RenderableNodes{}
for key, value := range rns {
result[key] = value.Copy()
}
return result
}
// Merge merges two sets of RenderableNodes, returning a new set.
func (rns RenderableNodes) Merge(other RenderableNodes) RenderableNodes {
result := RenderableNodes{}

View File

@@ -85,15 +85,12 @@ var ContainerRenderer = MakeReduce(
// but we need to be careful to ensure we only include each edge once, by only
// including the ProcessRenderer once.
Renderer: Filter{
f: func(n RenderableNode) bool {
FilterFunc: func(n RenderableNode) bool {
_, inContainer := n.Node.Metadata[docker.ContainerID]
_, isConnected := n.Node.Metadata[IsConnected]
return inContainer || isConnected
},
Renderer: CustomRenderer{
RenderFunc: ColorConnected,
Renderer: ProcessRenderer,
},
Renderer: ColorConnected(ProcessRenderer),
},
},

View File

@@ -4,6 +4,7 @@ import (
"reflect"
"testing"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/render/expected"
"github.com/weaveworks/scope/test"
@@ -33,6 +34,19 @@ func TestContainerRenderer(t *testing.T) {
}
}
func TestContainerFilterRenderer(t *testing.T) {
// tag on of the containers in the topology and ensure
// it is filtered out correctly.
input := test.Report.Copy()
input.Container.Nodes[test.ClientContainerNodeID].Metadata[docker.LabelPrefix+"works.weave.role"] = "system"
have := expected.Sterilize(render.FilterSystem(render.ContainerWithImageNameRenderer{}).Render(input))
want := expected.RenderedContainers.Copy()
delete(want, test.ClientContainerID)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestContainerImageRenderer(t *testing.T) {
have := expected.Sterilize(render.ContainerImageRenderer.Render(test.Report))
want := expected.RenderedContainerImages