mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 09:41:57 +00:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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{}{
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user