Fixing grouped node count for filtered children nodes

Squash of:

* We have to keep all the container hostnames until the end so we can
  count how many we've filtered

* Adding tests for ContainerHostnameRenderer and PodServiceRenderer with
  filters

* Because we filter on image name we need the image name before
  filtering

* Alternative approach to passing decorators.

* Refactor out some of the decorator capture

* Don't memoise decorated calls to Render

* Fixing filtered counts on containers topology

  Tricky, because we need the filters to be silent sometimes (when they're
  in the middle), but not when they're at the top, so we take the "top"
  filter's stats. However, this means we have to compose all
  user-specified filters into a single Filter layer, so we can get all
  stats.

  There are no more Silent filters, as all filters are silent (unless they
  are at the top).

  Additionally, I clarified some of the filters as their usage/terminology
  was inconsistent and confused. Now Filter(IsFoo, ...) *keeps* only nodes
  where IsFoo is true.
This commit is contained in:
Paul Bellamy
2016-04-21 17:48:56 +01:00
parent fdaafd2208
commit 3d3aed2bb3
27 changed files with 554 additions and 263 deletions

View File

@@ -38,22 +38,31 @@ func BenchmarkContainerWithImageNameRender(b *testing.B) {
func BenchmarkContainerWithImageNameStats(b *testing.B) {
benchmarkStats(b, render.ContainerWithImageNameRenderer)
}
func BenchmarkContainerImageRender(b *testing.B) { benchmarkRender(b, render.ContainerImageRenderer) }
func BenchmarkContainerImageStats(b *testing.B) { benchmarkStats(b, render.ContainerImageRenderer) }
func BenchmarkContainerImageRender(b *testing.B) {
benchmarkRender(b, render.ContainerImageRenderer)
}
func BenchmarkContainerImageStats(b *testing.B) {
benchmarkStats(b, render.ContainerImageRenderer)
}
func BenchmarkContainerHostnameRender(b *testing.B) {
benchmarkRender(b, render.ContainerHostnameRenderer)
}
func BenchmarkContainerHostnameStats(b *testing.B) {
benchmarkStats(b, render.ContainerHostnameRenderer)
}
func BenchmarkHostRender(b *testing.B) { benchmarkRender(b, render.HostRenderer) }
func BenchmarkHostStats(b *testing.B) { benchmarkStats(b, render.HostRenderer) }
func BenchmarkPodRender(b *testing.B) { benchmarkRender(b, render.PodRenderer) }
func BenchmarkPodStats(b *testing.B) { benchmarkStats(b, render.PodRenderer) }
func BenchmarkPodServiceRender(b *testing.B) { benchmarkRender(b, render.PodServiceRenderer) }
func BenchmarkPodServiceStats(b *testing.B) { benchmarkStats(b, render.PodServiceRenderer) }
func BenchmarkHostRender(b *testing.B) { benchmarkRender(b, render.HostRenderer) }
func BenchmarkHostStats(b *testing.B) { benchmarkStats(b, render.HostRenderer) }
func BenchmarkPodRender(b *testing.B) { benchmarkRender(b, render.PodRenderer) }
func BenchmarkPodStats(b *testing.B) { benchmarkStats(b, render.PodRenderer) }
func BenchmarkPodServiceRender(b *testing.B) {
benchmarkRender(b, render.PodServiceRenderer)
}
func BenchmarkPodServiceStats(b *testing.B) {
benchmarkStats(b, render.PodServiceRenderer)
}
func benchmarkRender(b *testing.B, r render.Renderer) {
report, err := loadReport()
if err != nil {
b.Fatal(err)
@@ -65,7 +74,7 @@ func benchmarkRender(b *testing.B, r render.Renderer) {
b.StopTimer()
render.ResetCache()
b.StartTimer()
benchmarkRenderResult = r.Render(report)
benchmarkRenderResult = r.Render(report, render.FilterNoop)
if len(benchmarkRenderResult) == 0 {
b.Errorf("Rendered topology contained no nodes")
}
@@ -85,7 +94,7 @@ func benchmarkStats(b *testing.B, r render.Renderer) {
b.StopTimer()
render.ResetCache()
b.StartTimer()
benchmarkStatsResult = r.Stats(report)
benchmarkStatsResult = r.Stats(report, render.FilterNoop)
}
}

View File

@@ -26,14 +26,14 @@ const (
// NB We only want processes in container _or_ processes with network connections
// but we need to be careful to ensure we only include each edge once, by only
// including the ProcessRenderer once.
var ContainerRenderer = MakeSilentFilter(
var ContainerRenderer = MakeFilter(
func(n report.Node) bool {
// Drop deleted containers
state, ok := n.Latest.Lookup(docker.ContainerState)
return !ok || state != docker.StateDeleted
},
MakeReduce(
MakeSilentFilter(
MakeFilter(
func(n report.Node) bool {
// Drop unconnected pseudo nodes (could appear due to filtering)
_, isConnected := n.Latest.Lookup(IsConnected)
@@ -49,7 +49,7 @@ var ContainerRenderer = MakeSilentFilter(
// We need to be careful to ensure we only include each edge once. Edges brought in
// by the above renders will have a pid, so its enough to filter out any nodes with
// pids.
SilentFilterUnconnected(MakeMap(
FilterUnconnected(MakeMap(
MapIP2Container,
MakeReduce(
MakeMap(
@@ -73,9 +73,9 @@ type containerWithHostIPsRenderer struct {
// 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) report.Nodes {
containers := r.Renderer.Render(rpt)
hosts := SelectHost.Render(rpt)
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 {
@@ -116,9 +116,9 @@ type containerWithImageNameRenderer struct {
// Render produces a process graph where the minor labels contain the
// container name, if found. It also merges the image node metadata into the
// container metadata.
func (r containerWithImageNameRenderer) Render(rpt report.Report) report.Nodes {
containers := r.Renderer.Render(rpt)
images := SelectContainerImage.Render(rpt)
func (r containerWithImageNameRenderer) Render(rpt report.Report, dct Decorator) report.Nodes {
containers := r.Renderer.Render(rpt, dct)
images := SelectContainerImage.Render(rpt, dct)
outputs := report.Nodes{}
for id, c := range containers {
@@ -140,23 +140,38 @@ func (r containerWithImageNameRenderer) Render(rpt report.Report) report.Nodes {
// ContainerWithImageNameRenderer is a Renderer which produces a container
// graph where the ranks are the image names, not their IDs
var ContainerWithImageNameRenderer = containerWithImageNameRenderer{ContainerWithHostIPsRenderer}
var ContainerWithImageNameRenderer = ApplyDecorators(containerWithImageNameRenderer{ContainerWithHostIPsRenderer})
// ContainerImageRenderer is a Renderer which produces a renderable container
// image graph by merging the container graph and the container image topology.
var ContainerImageRenderer = MakeReduce(
MakeMap(
MapContainer2ContainerImage,
ContainerRenderer,
var ContainerImageRenderer = FilterEmpty(report.Container,
MakeReduce(
MakeMap(
MapContainer2ContainerImage,
ContainerWithImageNameRenderer,
),
SelectContainerImage,
),
SelectContainerImage,
)
// ContainerHostnameRenderer is a Renderer which produces a renderable container
// by hostname graph..
var ContainerHostnameRenderer = MakeMap(
MapContainer2Hostname,
ContainerRenderer,
var ContainerHostnameRenderer = FilterEmpty(report.Container,
MakeReduce(
MakeMap(
MapContainer2Hostname,
ContainerWithImageNameRenderer,
),
// Grab *all* the hostnames, so we can count the number which were empty
// for accurate stats.
MakeMap(
MapToEmpty,
MakeMap(
MapContainer2Hostname,
ContainerRenderer,
),
),
),
)
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`)
@@ -364,3 +379,9 @@ func ImageNameWithoutVersion(name string) string {
parts = strings.SplitN(name, ":", 2)
return parts[0]
}
// MapToEmpty removes all the attributes, children, etc, of a node. Useful when
// we just want to count the presence of nodes.
func MapToEmpty(n report.Node, _ report.Networks) report.Nodes {
return report.Nodes{n.ID: report.MakeNode(n.ID).WithTopology(n.Topology)}
}

View File

@@ -47,7 +47,7 @@ func testMap(t *testing.T, f render.MapFunc, input testcase) {
}
func TestContainerRenderer(t *testing.T) {
have := Prune(render.ContainerRenderer.Render(fixture.Report))
have := Prune(render.ContainerWithImageNameRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedContainers)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -61,7 +61,7 @@ func TestContainerFilterRenderer(t *testing.T) {
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
docker.LabelPrefix + "works.weave.role": "system",
})
have := Prune(render.FilterSystem(render.ContainerRenderer).Render(input))
have := Prune(render.ContainerWithImageNameRenderer.Render(input, render.FilterApplication))
want := Prune(expected.RenderedContainers.Copy())
delete(want, fixture.ClientContainerNodeID)
if !reflect.DeepEqual(want, have) {
@@ -74,7 +74,7 @@ func TestContainerWithHostIPsRenderer(t *testing.T) {
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
docker.ContainerNetworkMode: "host",
})
nodes := render.ContainerWithHostIPsRenderer.Render(input)
nodes := render.ContainerWithHostIPsRenderer.Render(input, render.FilterNoop)
// Test host network nodes get the host IPs added.
haveNode, ok := nodes[fixture.ClientContainerNodeID]
@@ -91,8 +91,42 @@ func TestContainerWithHostIPsRenderer(t *testing.T) {
}
}
func TestContainerHostnameRenderer(t *testing.T) {
have := Prune(render.ContainerHostnameRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedContainerHostnames)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestContainerHostnameFilterRenderer(t *testing.T) {
// add a system container into the topology and ensure
// it is filtered out correctly.
input := fixture.Report.Copy()
clientContainer2ID := "f6g7h8i9j1"
clientContainer2NodeID := report.MakeContainerNodeID(clientContainer2ID)
input.Container.AddNode(report.MakeNodeWith(clientContainer2NodeID, map[string]string{
docker.LabelPrefix + "works.weave.role": "system",
docker.ContainerHostname: fixture.ClientContainerHostname,
report.HostNodeID: fixture.ClientHostNodeID,
}).
WithParents(report.EmptySets.
Add("host", report.MakeStringSet(fixture.ClientHostNodeID)),
).WithTopology(report.Container))
have := Prune(render.ContainerHostnameRenderer.Render(input, render.FilterApplication))
want := Prune(expected.RenderedContainerHostnames)
// Test works by virtue of the RenderedContainerHostname only having a container
// counter == 1
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestContainerImageRenderer(t *testing.T) {
have := Prune(render.ContainerImageRenderer.Render(fixture.Report))
have := Prune(render.ContainerImageRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedContainerImages)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -104,10 +138,8 @@ func TestContainerImageFilterRenderer(t *testing.T) {
// it is filtered out correctly.
input := fixture.Report.Copy()
// TODO: Add a process and endpoint here to make this test fail, so we can fix it.
clientContainer2ID = "f6g7h8i9j1"
clientContainer2NodeID = report.MakeContainerNodeID(fixture.ClientContainerID)
clientContainer2ID := "f6g7h8i9j1"
clientContainer2NodeID := report.MakeContainerNodeID(clientContainer2ID)
input.Container.AddNode(report.MakeNodeWith(clientContainer2NodeID, map[string]string{
docker.LabelPrefix + "works.weave.role": "system",
@@ -119,7 +151,7 @@ func TestContainerImageFilterRenderer(t *testing.T) {
Add("host", report.MakeStringSet(fixture.ClientHostNodeID)),
).WithTopology(report.ContainerImage))
have := Prune(render.FilterSystem(render.ContainerImageRenderer).Render(input))
have := Prune(render.ContainerImageRenderer.Render(input, render.FilterApplication))
want := Prune(expected.RenderedContainerImages.Copy())
// Test works by virtue of the RenderedContainerImage only having a container
// counter == 1

View File

@@ -16,7 +16,7 @@ import (
)
func child(t *testing.T, r render.Renderer, id string) detailed.NodeSummary {
s, ok := detailed.MakeNodeSummary(fixture.Report, r.Render(fixture.Report)[id])
s, ok := detailed.MakeNodeSummary(fixture.Report, r.Render(fixture.Report, render.FilterNoop)[id])
if !ok {
t.Fatalf("Expected node %s to be summarizable, but wasn't", id)
}
@@ -24,7 +24,7 @@ func child(t *testing.T, r render.Renderer, id string) detailed.NodeSummary {
}
func TestMakeDetailedHostNode(t *testing.T) {
renderableNodes := render.HostRenderer.Render(fixture.Report)
renderableNodes := render.HostRenderer.Render(fixture.Report, render.FilterNoop)
renderableNode := renderableNodes[fixture.ClientHostNodeID]
have := detailed.MakeNode("hosts", fixture.Report, renderableNodes, renderableNode)
@@ -171,7 +171,7 @@ func TestMakeDetailedHostNode(t *testing.T) {
func TestMakeDetailedContainerNode(t *testing.T) {
id := fixture.ServerContainerNodeID
renderableNodes := render.ContainerRenderer.Render(fixture.Report)
renderableNodes := render.ContainerRenderer.Render(fixture.Report, render.FilterNoop)
renderableNode, ok := renderableNodes[id]
if !ok {
t.Fatalf("Node not found: %s", id)
@@ -298,14 +298,14 @@ func TestMakeDetailedContainerNode(t *testing.T) {
func TestMakeDetailedPodNode(t *testing.T) {
id := fixture.ServerPodNodeID
renderableNodes := render.PodRenderer.Render(fixture.Report)
renderableNodes := render.PodRenderer.Render(fixture.Report, render.FilterNoop)
renderableNode, ok := renderableNodes[id]
if !ok {
t.Fatalf("Node not found: %s", id)
}
have := detailed.MakeNode("pods", fixture.Report, renderableNodes, renderableNode)
containerNodeSummary := child(t, render.ContainerRenderer, fixture.ServerContainerNodeID)
containerNodeSummary := child(t, render.ContainerWithImageNameRenderer, fixture.ServerContainerNodeID)
serverProcessNodeSummary := child(t, render.ProcessRenderer, fixture.ServerProcessNodeID)
serverProcessNodeSummary.Linkable = true // Temporary workaround for: https://github.com/weaveworks/scope/issues/1295
want := detailed.Node{

View File

@@ -20,30 +20,30 @@ func TestParents(t *testing.T) {
}{
{
name: "Node accidentally tagged with itself",
node: render.HostRenderer.Render(fixture.Report)[fixture.ClientHostNodeID].WithParents(
node: render.HostRenderer.Render(fixture.Report, render.FilterNoop)[fixture.ClientHostNodeID].WithParents(
report.EmptySets.Add(report.Host, report.MakeStringSet(fixture.ClientHostNodeID)),
),
want: nil,
},
{
node: render.HostRenderer.Render(fixture.Report)[fixture.ClientHostNodeID],
node: render.HostRenderer.Render(fixture.Report, render.FilterNoop)[fixture.ClientHostNodeID],
want: nil,
},
{
node: render.ContainerImageRenderer.Render(fixture.Report)[fixture.ClientContainerImageNodeID],
node: render.ContainerImageRenderer.Render(fixture.Report, render.FilterNoop)[fixture.ClientContainerImageNodeID],
want: []detailed.Parent{
{ID: fixture.ClientHostNodeID, Label: fixture.ClientHostName, TopologyID: "hosts"},
},
},
{
node: render.ContainerRenderer.Render(fixture.Report)[fixture.ClientContainerNodeID],
node: render.ContainerRenderer.Render(fixture.Report, render.FilterNoop)[fixture.ClientContainerNodeID],
want: []detailed.Parent{
{ID: fixture.ClientContainerImageNodeID, Label: fixture.ClientContainerImageName, TopologyID: "containers-by-image"},
{ID: fixture.ClientHostNodeID, Label: fixture.ClientHostName, TopologyID: "hosts"},
},
},
{
node: render.ProcessRenderer.Render(fixture.Report)[fixture.ClientProcess1NodeID],
node: render.ProcessRenderer.Render(fixture.Report, render.FilterNoop)[fixture.ClientProcess1NodeID],
want: []detailed.Parent{
{ID: fixture.ClientContainerNodeID, Label: fixture.ClientContainerName, TopologyID: "containers"},
{ID: fixture.ClientContainerImageNodeID, Label: fixture.ClientContainerImageName, TopologyID: "containers-by-image"},

View File

@@ -21,7 +21,7 @@ import (
func TestSummaries(t *testing.T) {
{
// Just a convenient source of some rendered nodes
have := detailed.Summaries(fixture.Report, render.ProcessRenderer.Render(fixture.Report))
have := detailed.Summaries(fixture.Report, render.ProcessRenderer.Render(fixture.Report, render.FilterNoop))
// The ids of the processes rendered above
expectedIDs := []string{
fixture.ClientProcess1NodeID,
@@ -51,7 +51,7 @@ func TestSummaries(t *testing.T) {
input := fixture.Report.Copy()
input.Process.Nodes[fixture.ClientProcess1NodeID] = input.Process.Nodes[fixture.ClientProcess1NodeID].WithMetrics(report.Metrics{process.CPUUsage: metric})
have := detailed.Summaries(input, render.ProcessRenderer.Render(input))
have := detailed.Summaries(input, render.ProcessRenderer.Render(input, render.FilterNoop))
node, ok := have[fixture.ClientProcess1NodeID]
if !ok {

View File

@@ -27,15 +27,16 @@ var (
return n
}
}
pseudo = node(render.Pseudo)
endpoint = node(report.Endpoint)
processNode = node(report.Process)
processNameNode = node(render.MakeGroupNodeTopology(report.Process, process.Name))
container = node(report.Container)
containerImage = node(report.ContainerImage)
pod = node(report.Pod)
service = node(report.Service)
hostNode = node(report.Host)
pseudo = node(render.Pseudo)
endpoint = node(report.Endpoint)
processNode = node(report.Process)
processNameNode = node(render.MakeGroupNodeTopology(report.Process, process.Name))
container = node(report.Container)
containerHostnameNode = node(render.MakeGroupNodeTopology(report.Container, docker.ContainerHostname))
containerImage = node(report.ContainerImage)
pod = node(report.Pod)
service = node(report.Service)
hostNode = node(report.Host)
UnknownPseudoNode1ID = render.MakePseudoNodeID(fixture.UnknownClient1IP)
UnknownPseudoNode2ID = render.MakePseudoNodeID(fixture.UnknownClient3IP)
@@ -176,6 +177,37 @@ var (
render.OutgoingInternetID: theOutgoingInternetNode,
}
RenderedContainerHostnames = report.Nodes{
fixture.ClientContainerHostname: containerHostnameNode(fixture.ClientContainerHostname, fixture.ServerContainerHostname).
WithLatests(map[string]string{
docker.ContainerHostname: fixture.ClientContainerHostname,
}).
WithCounters(map[string]int{
report.Container: 1,
}).
WithChildren(report.MakeNodeSet(
RenderedEndpoints[fixture.Client54001NodeID],
RenderedEndpoints[fixture.Client54002NodeID],
RenderedProcesses[fixture.ClientProcess1NodeID],
RenderedProcesses[fixture.ClientProcess2NodeID],
RenderedContainers[fixture.ClientContainerNodeID],
)),
fixture.ServerContainerHostname: containerHostnameNode(fixture.ServerContainerHostname).
WithLatests(map[string]string{
docker.ContainerHostname: fixture.ServerContainerHostname,
}).
WithChildren(report.MakeNodeSet(
RenderedEndpoints[fixture.Server80NodeID],
RenderedProcesses[fixture.ServerProcessNodeID],
RenderedContainers[fixture.ServerContainerNodeID],
)),
uncontainedServerID: uncontainedServerNode,
render.IncomingInternetID: theIncomingInternetNode(fixture.ServerContainerHostname),
render.OutgoingInternetID: theOutgoingInternetNode,
}
RenderedContainerImages = report.Nodes{
fixture.ClientContainerImageNodeID: containerImage(fixture.ClientContainerImageNodeID, fixture.ServerContainerImageNodeID).
WithLatests(map[string]string{

View File

@@ -19,8 +19,8 @@ type CustomRenderer struct {
}
// Render implements Renderer
func (c CustomRenderer) Render(rpt report.Report) report.Nodes {
return c.RenderFunc(c.Renderer.Render(rpt))
func (c CustomRenderer) Render(rpt report.Report, dct Decorator) report.Nodes {
return c.RenderFunc(c.Renderer.Render(rpt, dct))
}
// ColorConnected colors nodes with the IsConnected key if
@@ -55,41 +55,46 @@ func ColorConnected(r Renderer) Renderer {
}
}
// FilterFunc is the function type used by Filters
type FilterFunc func(report.Node) bool
// ComposeFilterFuncs composes filterfuncs into a single FilterFunc checking all.
func ComposeFilterFuncs(fs ...FilterFunc) FilterFunc {
return func(n report.Node) bool {
for _, f := range fs {
if !f(n) {
return false
}
}
return true
}
}
// Filter removes nodes from a view based on a predicate.
type Filter struct {
Renderer
FilterFunc func(report.Node) bool
Silent bool // true means we don't report stats for how many are filtered
FilterFunc FilterFunc
}
// MakeFilter makes a new Filter.
func MakeFilter(f func(report.Node) bool, r Renderer) Renderer {
func MakeFilter(f FilterFunc, r Renderer) Renderer {
return Memoise(&Filter{
Renderer: r,
FilterFunc: f,
})
}
// MakeSilentFilter makes a new Filter which does not report how many nodes it filters in Stats.
func MakeSilentFilter(f func(report.Node) bool, r Renderer) Renderer {
return Memoise(&Filter{
Renderer: r,
FilterFunc: f,
Silent: true,
})
}
// Render implements Renderer
func (f *Filter) Render(rpt report.Report) report.Nodes {
nodes, _ := f.render(rpt)
func (f *Filter) Render(rpt report.Report, dct Decorator) report.Nodes {
nodes, _ := f.render(rpt, dct)
return nodes
}
func (f *Filter) render(rpt report.Report) (report.Nodes, int) {
func (f *Filter) render(rpt report.Report, dct Decorator) (report.Nodes, int) {
output := report.Nodes{}
inDegrees := map[string]int{}
filtered := 0
for id, node := range f.Renderer.Render(rpt) {
for id, node := range f.Renderer.Render(rpt, dct) {
if f.FilterFunc(node) {
output[id] = node
inDegrees[id] = 0
@@ -126,14 +131,13 @@ func (f *Filter) render(rpt report.Report) (report.Nodes, int) {
return output, filtered
}
// Stats implements Renderer
func (f Filter) Stats(rpt report.Report) Stats {
var upstream = f.Renderer.Stats(rpt)
if !f.Silent {
_, filtered := f.render(rpt)
upstream.FilteredNodes += filtered
}
return upstream
// Stats implements Renderer. General logic is to take the first (i.e.
// highest-level) stats we find, so upstream stats are ignored. This means that
// if we want to count the stats from multiple filters we need to compose their
// FilterFuncs, into a single Filter.
func (f Filter) Stats(rpt report.Report, dct Decorator) Stats {
_, filtered := f.render(rpt, dct)
return Stats{FilteredNodes: filtered}
}
// IsConnected is the key added to Node.Metadata by ColorConnected
@@ -142,7 +146,7 @@ const IsConnected = "is_connected"
// Complement takes a FilterFunc f and returns a FilterFunc that has the same
// effects, if any, and returns the opposite truth value.
func Complement(f func(report.Node) bool) func(report.Node) bool {
func Complement(f FilterFunc) FilterFunc {
return func(node report.Node) bool { return !f(node) }
}
@@ -169,27 +173,11 @@ func FilterUnconnected(r Renderer) Renderer {
)
}
// SilentFilterUnconnected produces a renderer that filters unconnected nodes
// from the given renderer; nodes filtered by this are not reported in stats.
func SilentFilterUnconnected(r Renderer) Renderer {
return MakeSilentFilter(
func(node report.Node) bool {
_, ok := node.Latest.Lookup(IsConnected)
return ok
},
ColorConnected(r),
)
}
// Noop allows all nodes through
func Noop(_ report.Node) bool { return true }
// FilterNoop does nothing.
func FilterNoop(in Renderer) Renderer {
return in
}
// FilterStopped filters out stopped containers.
func FilterStopped(r Renderer) Renderer {
return MakeFilter(IsRunning, r)
}
func FilterNoop(r Renderer) Renderer { return r }
// IsRunning checks if the node is a running docker container
func IsRunning(n report.Node) bool {
@@ -197,14 +185,22 @@ func IsRunning(n report.Node) bool {
return !ok || (state == docker.StateRunning || state == docker.StateRestarting || state == docker.StatePaused)
}
// IsStopped checks if the node is *not* a running docker container
var IsStopped = Complement(IsRunning)
// FilterStopped filters out stopped containers.
func FilterStopped(r Renderer) Renderer {
return MakeFilter(IsStopped, r)
}
// FilterRunning filters out running containers.
func FilterRunning(r Renderer) Renderer {
return MakeFilter(Complement(IsRunning), r)
return MakeFilter(IsRunning, r)
}
// FilterNonProcspied removes endpoints which were not found in procspy.
func FilterNonProcspied(r Renderer) Renderer {
return MakeSilentFilter(
return MakeFilter(
func(node report.Node) bool {
_, ok := node.Latest.Lookup(endpoint.Procspied)
return ok
@@ -213,8 +209,8 @@ func FilterNonProcspied(r Renderer) Renderer {
)
}
// IsSystem checks if the node is a "system" node
func IsSystem(n report.Node) bool {
// IsApplication checks if the node is an "application" node
func IsApplication(n report.Node) bool {
containerName, _ := n.Latest.Lookup(docker.ContainerName)
if _, ok := systemContainerNames[containerName]; ok {
return false
@@ -239,14 +235,40 @@ func IsSystem(n report.Node) bool {
return true
}
// IsSystem checks if the node is a "system" node
var IsSystem = Complement(IsApplication)
// FilterSystem is a Renderer which filters out system nodes.
func FilterSystem(r Renderer) Renderer {
return MakeFilter(IsSystem, r)
}
// FilterApplication is a Renderer which filters out system nodes.
// FilterApplication is a Renderer which filters out application nodes.
func FilterApplication(r Renderer) Renderer {
return MakeFilter(Complement(IsSystem), r)
return MakeFilter(IsApplication, r)
}
// FilterEmpty is a Renderer which filters out nodes which have no children
// from the specified topology.
func FilterEmpty(topology string, r Renderer) Renderer {
return MakeFilter(HasChildren(topology), r)
}
// HasChildren returns true if the node has no children from the specified
// topology.
func HasChildren(topology string) FilterFunc {
return func(n report.Node) bool {
if n.Topology == Pseudo {
return true
}
count := 0
n.Children.ForEach(func(child report.Node) {
if child.Topology == topology {
count++
}
})
return count > 0
}
}
var systemContainerNames = map[string]struct{}{

View File

@@ -10,15 +10,13 @@ import (
)
func TestFilterRender(t *testing.T) {
renderer := render.FilterUnconnected(
mockRenderer{Nodes: report.Nodes{
"foo": report.MakeNode("foo").WithAdjacent("bar"),
"bar": report.MakeNode("bar").WithAdjacent("foo"),
"baz": report.MakeNode("baz"),
}})
renderer := mockRenderer{Nodes: report.Nodes{
"foo": report.MakeNode("foo").WithAdjacent("bar"),
"bar": report.MakeNode("bar").WithAdjacent("foo"),
"baz": report.MakeNode("baz"),
}}
have := report.MakeIDList()
for id := range renderer.Render(report.MakeReport()) {
for id := range renderer.Render(report.MakeReport(), render.FilterUnconnected) {
have = have.Add(id)
}
want := report.MakeIDList("foo", "bar")
@@ -29,18 +27,21 @@ func TestFilterRender(t *testing.T) {
func TestFilterRender2(t *testing.T) {
// Test adjacencies are removed for filtered nodes.
renderer := render.Filter{
FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
Renderer: mockRenderer{Nodes: report.Nodes{
"foo": report.MakeNode("foo").WithAdjacent("bar"),
"bar": report.MakeNode("bar").WithAdjacent("foo"),
"baz": report.MakeNode("baz"),
}},
filter := func(renderer render.Renderer) render.Renderer {
return &render.Filter{
FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
Renderer: renderer,
}
}
renderer := mockRenderer{Nodes: report.Nodes{
"foo": report.MakeNode("foo").WithAdjacent("bar"),
"bar": report.MakeNode("bar").WithAdjacent("foo"),
"baz": report.MakeNode("baz"),
}}
have := renderer.Render(report.MakeReport())
have := renderer.Render(report.MakeReport(), filter)
if have["foo"].Adjacency.Contains("bar") {
t.Error("adjacencies for removed nodes should have been removed")
}
@@ -55,46 +56,55 @@ func TestFilterUnconnectedPseudoNodes(t *testing.T) {
"bar": report.MakeNode("bar").WithAdjacent("baz"),
"baz": report.MakeNode("baz").WithTopology(render.Pseudo),
}
renderer := render.Filter{
FilterFunc: func(node report.Node) bool {
return true
},
Renderer: mockRenderer{Nodes: nodes},
renderer := mockRenderer{Nodes: nodes}
filter := func(renderer render.Renderer) render.Renderer {
return &render.Filter{
FilterFunc: func(node report.Node) bool {
return true
},
Renderer: renderer,
}
}
want := nodes
have := renderer.Render(report.MakeReport())
have := renderer.Render(report.MakeReport(), filter)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
{
renderer := render.Filter{
FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
Renderer: mockRenderer{Nodes: report.Nodes{
"foo": report.MakeNode("foo").WithAdjacent("bar"),
"bar": report.MakeNode("bar").WithAdjacent("baz"),
"baz": report.MakeNode("baz").WithTopology(render.Pseudo),
}},
filter := func(renderer render.Renderer) render.Renderer {
return &render.Filter{
FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
Renderer: renderer,
}
}
have := renderer.Render(report.MakeReport())
renderer := mockRenderer{Nodes: report.Nodes{
"foo": report.MakeNode("foo").WithAdjacent("bar"),
"bar": report.MakeNode("bar").WithAdjacent("baz"),
"baz": report.MakeNode("baz").WithTopology(render.Pseudo),
}}
have := renderer.Render(report.MakeReport(), filter)
if _, ok := have["baz"]; ok {
t.Error("expected the unconnected pseudonode baz to have been removed")
}
}
{
renderer := render.Filter{
FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
Renderer: mockRenderer{Nodes: report.Nodes{
"foo": report.MakeNode("foo"),
"bar": report.MakeNode("bar").WithAdjacent("foo"),
"baz": report.MakeNode("baz").WithTopology(render.Pseudo).WithAdjacent("bar"),
}},
filter := func(renderer render.Renderer) render.Renderer {
return &render.Filter{
FilterFunc: func(node report.Node) bool {
return node.ID != "bar"
},
Renderer: renderer,
}
}
have := renderer.Render(report.MakeReport())
renderer := mockRenderer{Nodes: report.Nodes{
"foo": report.MakeNode("foo"),
"bar": report.MakeNode("bar").WithAdjacent("foo"),
"baz": report.MakeNode("baz").WithTopology(render.Pseudo).WithAdjacent("bar"),
}}
have := renderer.Render(report.MakeReport(), filter)
if _, ok := have["baz"]; ok {
t.Error("expected the unconnected pseudonode baz to have been removed")
}
@@ -107,8 +117,8 @@ func TestFilterUnconnectedSelf(t *testing.T) {
nodes := report.Nodes{
"foo": report.MakeNode("foo").WithAdjacent("foo"),
}
renderer := render.FilterUnconnected(mockRenderer{Nodes: nodes})
have := renderer.Render(report.MakeReport())
renderer := mockRenderer{Nodes: nodes}
have := renderer.Render(report.MakeReport(), render.FilterUnconnected)
if len(have) > 0 {
t.Error("expected node only connected to self to be removed")
}
@@ -122,8 +132,8 @@ func TestFilterPseudo(t *testing.T) {
"foo": report.MakeNode("foo"),
"bar": report.MakeNode("bar").WithTopology(render.Pseudo),
}
renderer := render.FilterPseudo(mockRenderer{Nodes: nodes})
have := renderer.Render(report.MakeReport())
renderer := mockRenderer{Nodes: nodes}
have := renderer.Render(report.MakeReport(), render.FilterPseudo)
if _, ok := have["bar"]; ok {
t.Error("expected pseudonode to be removed")
}

View File

@@ -11,7 +11,7 @@ import (
)
func TestHostRenderer(t *testing.T) {
have := Prune(render.HostRenderer.Render(fixture.Report))
have := Prune(render.HostRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedHosts)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))

View File

@@ -27,13 +27,17 @@ func Memoise(r Renderer) Renderer {
// Render produces a set of Nodes given a Report.
// Ideally, it just retrieves it from the cache, otherwise it calls through to
// `r` and stores the result.
func (m *memoise) Render(rpt report.Report) report.Nodes {
func (m *memoise) Render(rpt report.Report, dct Decorator) report.Nodes {
key := fmt.Sprintf("%s-%s", rpt.ID, m.id)
if result, err := renderCache.Get(key); err == nil {
return result.(report.Nodes)
if dct == nil {
if result, err := renderCache.Get(key); err == nil {
return result.(report.Nodes)
}
}
output := m.Renderer.Render(rpt, dct)
if dct == nil {
renderCache.Set(key, output)
}
output := m.Renderer.Render(rpt)
renderCache.Set(key, output)
return output
}

View File

@@ -11,8 +11,8 @@ import (
type renderFunc func(r report.Report) report.Nodes
func (f renderFunc) Render(r report.Report) report.Nodes { return f(r) }
func (f renderFunc) Stats(r report.Report) render.Stats { return render.Stats{} }
func (f renderFunc) Render(r report.Report, _ render.Decorator) report.Nodes { return f(r) }
func (f renderFunc) Stats(r report.Report, _ render.Decorator) render.Stats { return render.Stats{} }
func TestMemoise(t *testing.T) {
calls := 0
@@ -23,7 +23,7 @@ func TestMemoise(t *testing.T) {
m := render.Memoise(r)
rpt1 := report.MakeReport()
result1 := m.Render(rpt1)
result1 := m.Render(rpt1, nil)
// it should have rendered it.
if _, ok := result1[rpt1.ID]; !ok {
t.Errorf("Expected rendered report to contain a node, but got: %v", result1)
@@ -32,7 +32,7 @@ func TestMemoise(t *testing.T) {
t.Errorf("Expected renderer to have been called the first time")
}
result2 := m.Render(rpt1)
result2 := m.Render(rpt1, nil)
if !reflect.DeepEqual(result1, result2) {
t.Errorf("Expected memoised result to be returned: %s", test.Diff(result1, result2))
}
@@ -41,7 +41,7 @@ func TestMemoise(t *testing.T) {
}
rpt2 := report.MakeReport()
result3 := m.Render(rpt2)
result3 := m.Render(rpt2, nil)
if reflect.DeepEqual(result1, result3) {
t.Errorf("Expected different result for different report, but were the same")
}
@@ -50,7 +50,7 @@ func TestMemoise(t *testing.T) {
}
render.ResetCache()
result4 := m.Render(rpt1)
result4 := m.Render(rpt1, nil)
if !reflect.DeepEqual(result1, result4) {
t.Errorf("Expected original result to be returned: %s", test.Diff(result1, result4))
}

View File

@@ -15,29 +15,33 @@ const (
// PodRenderer is a Renderer which produces a renderable kubernetes
// graph by merging the container graph and the pods topology.
var PodRenderer = MakeReduce(
MakeSilentFilter(
func(n report.Node) bool {
// Drop unconnected pseudo nodes (could appear due to filtering)
_, isConnected := n.Latest.Lookup(IsConnected)
return n.Topology != Pseudo || isConnected
},
ColorConnected(MakeMap(
MapContainer2Pod,
ContainerRenderer,
)),
var PodRenderer = FilterEmpty(report.Container,
MakeReduce(
MakeFilter(
func(n report.Node) bool {
// Drop unconnected pseudo nodes (could appear due to filtering)
_, isConnected := n.Latest.Lookup(IsConnected)
return n.Topology != Pseudo || isConnected
},
ColorConnected(MakeMap(
MapContainer2Pod,
ContainerWithImageNameRenderer,
)),
),
SelectPod,
),
SelectPod,
)
// PodServiceRenderer is a Renderer which produces a renderable kubernetes services
// graph by merging the pods graph and the services topology.
var PodServiceRenderer = MakeReduce(
MakeMap(
MapPod2Service,
PodRenderer,
var PodServiceRenderer = FilterEmpty(report.Pod,
MakeReduce(
MakeMap(
MapPod2Service,
PodRenderer,
),
SelectService,
),
SelectService,
)
// MapContainer2Pod maps container Nodes to pod

View File

@@ -7,13 +7,14 @@ import (
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/render/expected"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/fixture"
"github.com/weaveworks/scope/test/reflect"
)
func TestPodRenderer(t *testing.T) {
have := Prune(render.PodRenderer.Render(fixture.Report))
have := Prune(render.PodRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedPods)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -32,7 +33,7 @@ func TestPodFilterRenderer(t *testing.T) {
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
docker.LabelPrefix + "io.kubernetes.pod.name": "kube-system/foo",
})
have := Prune(render.FilterSystem(render.PodRenderer).Render(input))
have := Prune(render.PodRenderer.Render(input, render.FilterApplication))
want := Prune(expected.RenderedPods.Copy())
delete(want, fixture.ClientPodNodeID)
delete(want, fixture.ClientContainerNodeID)
@@ -42,9 +43,37 @@ func TestPodFilterRenderer(t *testing.T) {
}
func TestPodServiceRenderer(t *testing.T) {
have := Prune(render.PodServiceRenderer.Render(fixture.Report))
have := Prune(render.PodServiceRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedPodServices)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestPodServiceFilterRenderer(t *testing.T) {
// tag on containers or pod namespace in the topology and ensure
// it is filtered out correctly.
input := fixture.Report.Copy()
input.Pod.Nodes[fixture.ClientPodNodeID] = input.Pod.Nodes[fixture.ClientPodNodeID].WithLatests(map[string]string{
kubernetes.PodID: "pod:kube-system/foo",
kubernetes.Namespace: "kube-system",
kubernetes.PodName: "foo",
})
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
docker.LabelPrefix + "io.kubernetes.pod.name": "kube-system/foo",
})
have := Prune(render.PodServiceRenderer.Render(input, render.FilterApplication))
want := Prune(expected.RenderedPodServices.Copy())
wantNode := want[fixture.ServiceNodeID]
wantNode.Adjacency = nil
wantNode.Children = report.MakeNodeSet(
expected.RenderedEndpoints[fixture.Server80NodeID],
expected.RenderedProcesses[fixture.ServerProcessNodeID],
expected.RenderedContainers[fixture.ServerContainerNodeID],
expected.RenderedPods[fixture.ServerPodNodeID],
)
want[fixture.ServiceNodeID] = wantNode
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}

View File

@@ -42,9 +42,9 @@ type processWithContainerNameRenderer struct {
Renderer
}
func (r processWithContainerNameRenderer) Render(rpt report.Report) report.Nodes {
processes := r.Renderer.Render(rpt)
containers := SelectContainer.Render(rpt)
func (r processWithContainerNameRenderer) Render(rpt report.Report, dct Decorator) report.Nodes {
processes := r.Renderer.Render(rpt, dct)
containers := SelectContainer.Render(rpt, dct)
outputs := report.Nodes{}
for id, p := range processes {

View File

@@ -11,7 +11,7 @@ import (
)
func TestEndpointRenderer(t *testing.T) {
have := Prune(render.EndpointRenderer.Render(fixture.Report))
have := Prune(render.EndpointRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedEndpoints)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -19,7 +19,7 @@ func TestEndpointRenderer(t *testing.T) {
}
func TestProcessRenderer(t *testing.T) {
have := Prune(render.ProcessRenderer.Render(fixture.Report))
have := Prune(render.ProcessRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedProcesses)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -27,7 +27,7 @@ func TestProcessRenderer(t *testing.T) {
}
func TestProcessNameRenderer(t *testing.T) {
have := Prune(render.ProcessNameRenderer.Render(fixture.Report))
have := Prune(render.ProcessNameRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedProcessNames)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))

View File

@@ -12,8 +12,8 @@ type MapFunc func(report.Node, report.Networks) report.Nodes
// Renderer is something that can render a report to a set of Nodes.
type Renderer interface {
Render(report.Report) report.Nodes
Stats(report.Report) Stats
Render(report.Report, Decorator) report.Nodes
Stats(report.Report, Decorator) Stats
}
// Stats is the type returned by Renderer.Stats
@@ -38,19 +38,19 @@ func MakeReduce(renderers ...Renderer) Renderer {
}
// Render produces a set of Nodes given a Report.
func (r *Reduce) Render(rpt report.Report) report.Nodes {
func (r *Reduce) Render(rpt report.Report, dct Decorator) report.Nodes {
result := report.Nodes{}
for _, renderer := range *r {
result = result.Merge(renderer.Render(rpt))
result = result.Merge(renderer.Render(rpt, dct))
}
return result
}
// Stats implements Renderer
func (r *Reduce) Stats(rpt report.Report) Stats {
func (r *Reduce) Stats(rpt report.Report, dct Decorator) Stats {
var result Stats
for _, renderer := range *r {
result = result.merge(renderer.Stats(rpt))
result = result.merge(renderer.Stats(rpt, dct))
}
return result
}
@@ -69,9 +69,9 @@ func MakeMap(f MapFunc, r Renderer) Renderer {
// Render transforms a set of Nodes produces by another Renderer.
// using a map function
func (m *Map) Render(rpt report.Report) report.Nodes {
func (m *Map) Render(rpt report.Report, dct Decorator) report.Nodes {
var (
input = m.Renderer.Render(rpt)
input = m.Renderer.Render(rpt, dct)
output = report.Nodes{}
mapped = map[string]report.IDList{} // input node ID -> output node IDs
adjacencies = map[string]report.IDList{} // output node ID -> input node Adjacencies
@@ -109,9 +109,45 @@ func (m *Map) Render(rpt report.Report) report.Nodes {
}
// Stats implements Renderer
func (m *Map) Stats(rpt report.Report) Stats {
func (m *Map) Stats(_ report.Report, _ Decorator) Stats {
// There doesn't seem to be an instance where we want stats to recurse
// through Maps - for instance we don't want to see the number of filtered
// processes in the container renderer.
return Stats{}
}
// Decorator transforms one renderer to another. e.g. Filters.
type Decorator func(Renderer) Renderer
// ComposeDecorators composes decorators into one.
func ComposeDecorators(decorators ...Decorator) Decorator {
return func(r Renderer) Renderer {
for _, decorator := range decorators {
r = decorator(r)
}
return r
}
}
type applyDecorator struct {
Renderer
}
func (ad applyDecorator) Render(rpt report.Report, dct Decorator) report.Nodes {
if dct != nil {
return dct(ad.Renderer).Render(rpt, nil)
}
return ad.Renderer.Render(rpt, nil)
}
func (ad applyDecorator) Stats(rpt report.Report, dct Decorator) Stats {
if dct != nil {
return dct(ad.Renderer).Stats(rpt, nil)
}
return ad.Renderer.Stats(rpt, nil)
}
// ApplyDecorators returns a renderer which will apply the given decorators
// to the child render.
func ApplyDecorators(renderer Renderer) Renderer {
return applyDecorator{renderer}
}

View File

@@ -13,8 +13,13 @@ type mockRenderer struct {
report.Nodes
}
func (m mockRenderer) Render(rpt report.Report) report.Nodes { return m.Nodes }
func (m mockRenderer) Stats(rpt report.Report) render.Stats { return render.Stats{} }
func (m mockRenderer) Render(rpt report.Report, d render.Decorator) report.Nodes {
if d != nil {
return d(mockRenderer{m.Nodes}).Render(rpt, nil)
}
return m.Nodes
}
func (m mockRenderer) Stats(rpt report.Report, _ render.Decorator) render.Stats { return render.Stats{} }
// Prune returns a copy of the Nodes with all information not strictly
// necessary for rendering nodes and edges in the UI cut away.
@@ -51,7 +56,7 @@ func TestReduceRender(t *testing.T) {
"foo": report.MakeNode("foo"),
"bar": report.MakeNode("bar"),
}
have := renderer.Render(report.MakeReport())
have := renderer.Render(report.MakeReport(), render.FilterNoop)
if !reflect.DeepEqual(want, have) {
t.Errorf("want %+v, have %+v", want, have)
}
@@ -68,7 +73,7 @@ func TestMapRender1(t *testing.T) {
}},
}
want := report.Nodes{}
have := mapper.Render(report.MakeReport())
have := mapper.Render(report.MakeReport(), render.FilterNoop)
if !reflect.DeepEqual(want, have) {
t.Errorf("want %+v, have %+v", want, have)
}
@@ -90,7 +95,7 @@ func TestMapRender2(t *testing.T) {
want := report.Nodes{
"bar": report.MakeNode("bar"),
}
have := mapper.Render(report.MakeReport())
have := mapper.Render(report.MakeReport(), render.FilterNoop)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
@@ -112,7 +117,7 @@ func TestMapRender3(t *testing.T) {
"_foo": report.MakeNode("_foo").WithAdjacent("_baz"),
"_baz": report.MakeNode("_baz").WithAdjacent("_foo"),
}
have := mapper.Render(report.MakeReport())
have := mapper.Render(report.MakeReport(), render.FilterNoop)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}

View File

@@ -9,13 +9,13 @@ import (
type TopologySelector string
// Render implements Renderer
func (t TopologySelector) Render(r report.Report) report.Nodes {
func (t TopologySelector) Render(r report.Report, _ Decorator) report.Nodes {
topology, _ := r.Topology(string(t))
return topology.Nodes
}
// Stats implements Renderer
func (t TopologySelector) Stats(r report.Report) Stats {
func (t TopologySelector) Stats(r report.Report, _ Decorator) Stats {
return Stats{}
}

View File

@@ -73,7 +73,7 @@ var (
)
func TestShortLivedInternetNodeConnections(t *testing.T) {
have := Prune(render.ContainerWithImageNameRenderer.Render(rpt))
have := Prune(render.ContainerWithImageNameRenderer.Render(rpt, render.FilterNoop))
// Conntracked-only connections from the internet should be assigned to the internet pseudonode
internet, ok := have[render.IncomingInternetID]