mirror of
https://github.com/weaveworks/scope.git
synced 2026-08-18 20:07:06 +00:00
Merge pull request #1371 from weaveworks/1219-grouped-node-counts-2
Fixing grouped node count for filtered children nodes
This commit is contained in:
+34
-39
@@ -26,9 +26,9 @@ func init() {
|
||||
ID: "system",
|
||||
Default: "application",
|
||||
Options: []APITopologyOption{
|
||||
{"system", "System services", render.FilterApplication},
|
||||
{"application", "Application services", render.FilterSystem},
|
||||
{"both", "Both", render.FilterNoop},
|
||||
{"system", "System services", render.IsSystem},
|
||||
{"application", "Application services", render.IsApplication},
|
||||
{"both", "Both", render.Noop},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -38,9 +38,9 @@ func init() {
|
||||
ID: "system",
|
||||
Default: "application",
|
||||
Options: []APITopologyOption{
|
||||
{"system", "System pods", render.FilterApplication},
|
||||
{"application", "Application pods", render.FilterSystem},
|
||||
{"both", "Both", render.FilterNoop},
|
||||
{"system", "System pods", render.IsSystem},
|
||||
{"application", "Application pods", render.IsApplication},
|
||||
{"both", "Both", render.Noop},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -50,18 +50,18 @@ func init() {
|
||||
ID: "system",
|
||||
Default: "application",
|
||||
Options: []APITopologyOption{
|
||||
{"system", "System containers", render.FilterApplication},
|
||||
{"application", "Application containers", render.FilterSystem},
|
||||
{"both", "Both", render.FilterNoop},
|
||||
{"system", "System containers", render.IsSystem},
|
||||
{"application", "Application containers", render.IsApplication},
|
||||
{"both", "Both", render.Noop},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "stopped",
|
||||
Default: "running",
|
||||
Options: []APITopologyOption{
|
||||
{"stopped", "Stopped containers", render.FilterRunning},
|
||||
{"running", "Running containers", render.FilterStopped},
|
||||
{"both", "Both", render.FilterNoop},
|
||||
{"stopped", "Stopped containers", render.IsStopped},
|
||||
{"running", "Running containers", render.IsRunning},
|
||||
{"both", "Both", render.Noop},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func init() {
|
||||
Options: []APITopologyOption{
|
||||
// Show the user why there are filtered nodes in this view.
|
||||
// Don't give them the option to show those nodes.
|
||||
{"hide", "Unconnected nodes hidden", render.FilterNoop},
|
||||
{"hide", "Unconnected nodes hidden", render.Noop},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -181,7 +181,7 @@ type APITopologyOption struct {
|
||||
Value string `json:"value"`
|
||||
Label string `json:"label"`
|
||||
|
||||
decorator func(render.Renderer) render.Renderer
|
||||
filter render.FilterFunc
|
||||
}
|
||||
|
||||
type topologyStats struct {
|
||||
@@ -247,31 +247,31 @@ func (r *registry) makeTopologyList(rep Reporter) CtxHandlerFunc {
|
||||
func (r *registry) renderTopologies(rpt report.Report, req *http.Request) []APITopologyDesc {
|
||||
topologies := []APITopologyDesc{}
|
||||
r.walk(func(desc APITopologyDesc) {
|
||||
renderer := renderedForRequest(req, desc)
|
||||
desc.Stats = decorateWithStats(rpt, renderer)
|
||||
renderer, decorator := renderedForRequest(req, desc)
|
||||
desc.Stats = decorateWithStats(rpt, renderer, decorator)
|
||||
for i := range desc.SubTopologies {
|
||||
renderer := renderedForRequest(req, desc.SubTopologies[i])
|
||||
desc.SubTopologies[i].Stats = decorateWithStats(rpt, renderer)
|
||||
renderer, decorator := renderedForRequest(req, desc.SubTopologies[i])
|
||||
desc.SubTopologies[i].Stats = decorateWithStats(rpt, renderer, decorator)
|
||||
}
|
||||
topologies = append(topologies, desc)
|
||||
})
|
||||
return topologies
|
||||
}
|
||||
|
||||
func decorateWithStats(rpt report.Report, renderer render.Renderer) topologyStats {
|
||||
func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator render.Decorator) topologyStats {
|
||||
var (
|
||||
nodes int
|
||||
realNodes int
|
||||
edges int
|
||||
)
|
||||
for _, n := range renderer.Render(rpt) {
|
||||
for _, n := range renderer.Render(rpt, decorator) {
|
||||
nodes++
|
||||
if n.Topology != render.Pseudo {
|
||||
realNodes++
|
||||
}
|
||||
edges += len(n.Adjacency)
|
||||
}
|
||||
renderStats := renderer.Stats(rpt)
|
||||
renderStats := renderer.Stats(rpt, decorator)
|
||||
return topologyStats{
|
||||
NodeCount: nodes,
|
||||
NonpseudoNodeCount: realNodes,
|
||||
@@ -280,20 +280,26 @@ func decorateWithStats(rpt report.Report, renderer render.Renderer) topologyStat
|
||||
}
|
||||
}
|
||||
|
||||
func renderedForRequest(r *http.Request, topology APITopologyDesc) render.Renderer {
|
||||
renderer := topology.renderer
|
||||
func renderedForRequest(r *http.Request, topology APITopologyDesc) (render.Renderer, render.Decorator) {
|
||||
var filters []render.FilterFunc
|
||||
for _, group := range topology.Options {
|
||||
value := r.FormValue(group.ID)
|
||||
for _, opt := range group.Options {
|
||||
if (value == "" && group.Default == opt.Value) || (opt.Value != "" && opt.Value == value) {
|
||||
renderer = opt.decorator(renderer)
|
||||
filters = append(filters, opt.filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
return renderer
|
||||
return topology.renderer, func(renderer render.Renderer) render.Renderer {
|
||||
return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
|
||||
}
|
||||
}
|
||||
|
||||
type reportRenderHandler func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request)
|
||||
type reportRenderHandler func(
|
||||
context.Context,
|
||||
Reporter, render.Renderer, render.Decorator,
|
||||
http.ResponseWriter, *http.Request,
|
||||
)
|
||||
|
||||
func (r *registry) captureRenderer(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
@@ -302,18 +308,7 @@ func (r *registry) captureRenderer(rep Reporter, f reportRenderHandler) CtxHandl
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
renderer := renderedForRequest(req, topology)
|
||||
f(ctx, rep, renderer, w, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) captureRendererWithoutFilters(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
topology, ok := r.get(mux.Vars(req)["topology"])
|
||||
if !ok {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
f(ctx, rep, topology.renderer, w, req)
|
||||
renderer, decorator := renderedForRequest(req, topology)
|
||||
f(ctx, rep, renderer, decorator, w, req)
|
||||
}
|
||||
}
|
||||
|
||||
+20
-7
@@ -28,19 +28,27 @@ type APINode struct {
|
||||
}
|
||||
|
||||
// Full topology.
|
||||
func handleTopology(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
|
||||
func handleTopology(
|
||||
ctx context.Context,
|
||||
rep Reporter, renderer render.Renderer, decorator render.Decorator,
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
report, err := rep.Report(ctx)
|
||||
if err != nil {
|
||||
respondWith(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
respondWith(w, http.StatusOK, APITopology{
|
||||
Nodes: detailed.Summaries(report, renderer.Render(report)),
|
||||
Nodes: detailed.Summaries(report, renderer.Render(report, decorator)),
|
||||
})
|
||||
}
|
||||
|
||||
// Websocket for the full topology. This route overlaps with the next.
|
||||
func handleWs(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
|
||||
func handleWs(
|
||||
ctx context.Context,
|
||||
rep Reporter, renderer render.Renderer, decorator render.Decorator,
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
respondWith(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
@@ -53,17 +61,21 @@ func handleWs(ctx context.Context, rep Reporter, renderer render.Renderer, w htt
|
||||
return
|
||||
}
|
||||
}
|
||||
handleWebsocket(ctx, w, r, rep, renderer, loop)
|
||||
handleWebsocket(ctx, w, r, rep, renderer, decorator, loop)
|
||||
}
|
||||
|
||||
// Individual nodes.
|
||||
func handleNode(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
|
||||
func handleNode(
|
||||
ctx context.Context,
|
||||
rep Reporter, renderer render.Renderer, _ render.Decorator,
|
||||
w http.ResponseWriter, r *http.Request,
|
||||
) {
|
||||
var (
|
||||
vars = mux.Vars(r)
|
||||
topologyID = vars["topology"]
|
||||
nodeID = vars["id"]
|
||||
report, err = rep.Report(ctx)
|
||||
rendered = renderer.Render(report)
|
||||
rendered = renderer.Render(report, render.FilterNoop)
|
||||
node, ok = rendered[nodeID]
|
||||
)
|
||||
if err != nil {
|
||||
@@ -83,6 +95,7 @@ func handleWebsocket(
|
||||
r *http.Request,
|
||||
rep Reporter,
|
||||
renderer render.Renderer,
|
||||
decorator render.Decorator,
|
||||
loop time.Duration,
|
||||
) {
|
||||
conn, err := xfer.Upgrade(w, r, nil)
|
||||
@@ -119,7 +132,7 @@ func handleWebsocket(
|
||||
log.Errorf("Error generating report: %v", err)
|
||||
return
|
||||
}
|
||||
newTopo := detailed.Summaries(report, renderer.Render(report))
|
||||
newTopo := detailed.Summaries(report, renderer.Render(report, decorator))
|
||||
diff := detailed.TopoDiff(previousTopo, newTopo)
|
||||
previousTopo = newTopo
|
||||
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ func RegisterTopologyRoutes(router *mux.Router, r Reporter) {
|
||||
get.HandleFunc("/api/topology/{topology}/ws",
|
||||
requestContextDecorator(topologyRegistry.captureRenderer(r, handleWs))) // NB not gzip!
|
||||
get.MatcherFunc(URLMatcher("/api/topology/{topology}/{id}")).HandlerFunc(
|
||||
gzipHandler(requestContextDecorator(topologyRegistry.captureRendererWithoutFilters(r, handleNode))))
|
||||
gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleNode))))
|
||||
get.HandleFunc("/api/report",
|
||||
gzipHandler(requestContextDecorator(makeRawReportHandler(r))))
|
||||
get.HandleFunc("/api/probes",
|
||||
|
||||
@@ -19,5 +19,5 @@ func renderTo(rpt report.Report, topology string) (detailed.NodeSummaries, error
|
||||
if !ok {
|
||||
return detailed.NodeSummaries{}, fmt.Errorf("unknown topology %v", topology)
|
||||
}
|
||||
return detailed.Summaries(rpt, renderer.Render(rpt)), nil
|
||||
return detailed.Summaries(rpt, renderer.Render(rpt, render.FilterNoop)), nil
|
||||
}
|
||||
|
||||
+19
-10
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+39
-18
@@ -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,8 +47,8 @@ func testMap(t *testing.T, f render.MapFunc, input testcase) {
|
||||
}
|
||||
|
||||
func TestContainerRenderer(t *testing.T) {
|
||||
have := render.ContainerRenderer.Render(fixture.Report).Prune()
|
||||
want := expected.RenderedContainers.Prune()
|
||||
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,8 +61,8 @@ 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 := render.FilterSystem(render.ContainerRenderer).Render(input).Prune()
|
||||
want := expected.RenderedContainers.Copy().Prune()
|
||||
have := Prune(render.ContainerWithImageNameRenderer.Render(input, render.FilterApplication))
|
||||
want := Prune(expected.RenderedContainers.Copy())
|
||||
delete(want, fixture.ClientContainerNodeID)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(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,9 +91,71 @@ 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 := render.ContainerImageRenderer.Render(fixture.Report).Prune()
|
||||
want := expected.RenderedContainerImages.Prune()
|
||||
have := Prune(render.ContainerImageRenderer.Render(fixture.Report, render.FilterNoop))
|
||||
want := Prune(expected.RenderedContainerImages)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerImageFilterRenderer(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.ImageID: fixture.ClientContainerImageID,
|
||||
docker.ImageName: fixture.ClientContainerImageName,
|
||||
report.HostNodeID: fixture.ClientHostNodeID,
|
||||
}).
|
||||
WithParents(report.EmptySets.
|
||||
Add("host", report.MakeStringSet(fixture.ClientHostNodeID)),
|
||||
).WithTopology(report.ContainerImage))
|
||||
|
||||
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
|
||||
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
+74
-52
@@ -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
|
||||
@@ -243,14 +239,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{}{
|
||||
|
||||
+58
-48
@@ -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")
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func TestHostRenderer(t *testing.T) {
|
||||
have := render.HostRenderer.Render(fixture.Report).Prune()
|
||||
want := expected.RenderedHosts.Prune()
|
||||
have := Prune(render.HostRenderer.Render(fixture.Report, render.FilterNoop))
|
||||
want := Prune(expected.RenderedHosts)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
|
||||
+9
-5
@@ -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))
|
||||
}
|
||||
|
||||
+21
-17
@@ -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
|
||||
|
||||
+35
-6
@@ -7,14 +7,15 @@ 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 := render.PodRenderer.Render(fixture.Report).Prune()
|
||||
want := expected.RenderedPods.Prune()
|
||||
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,8 +33,8 @@ 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 := render.FilterSystem(render.PodRenderer).Render(input).Prune()
|
||||
want := expected.RenderedPods.Copy().Prune()
|
||||
have := Prune(render.PodRenderer.Render(input, render.FilterApplication))
|
||||
want := Prune(expected.RenderedPods.Copy())
|
||||
delete(want, fixture.ClientPodNodeID)
|
||||
delete(want, fixture.ClientContainerNodeID)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
@@ -42,8 +43,36 @@ func TestPodFilterRenderer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPodServiceRenderer(t *testing.T) {
|
||||
have := render.PodServiceRenderer.Render(fixture.Report).Prune()
|
||||
want := expected.RenderedPodServices.Prune()
|
||||
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))
|
||||
}
|
||||
|
||||
+3
-3
@@ -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,24 +11,24 @@ import (
|
||||
)
|
||||
|
||||
func TestEndpointRenderer(t *testing.T) {
|
||||
have := render.EndpointRenderer.Render(fixture.Report).Prune()
|
||||
want := expected.RenderedEndpoints.Prune()
|
||||
have := Prune(render.EndpointRenderer.Render(fixture.Report, render.FilterNoop))
|
||||
want := Prune(expected.RenderedEndpoints)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessRenderer(t *testing.T) {
|
||||
have := render.ProcessRenderer.Render(fixture.Report).Prune()
|
||||
want := expected.RenderedProcesses.Prune()
|
||||
have := Prune(render.ProcessRenderer.Render(fixture.Report, render.FilterNoop))
|
||||
want := Prune(expected.RenderedProcesses)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessNameRenderer(t *testing.T) {
|
||||
have := render.ProcessNameRenderer.Render(fixture.Report).Prune()
|
||||
want := expected.RenderedProcessNames.Prune()
|
||||
have := Prune(render.ProcessNameRenderer.Render(fixture.Report, render.FilterNoop))
|
||||
want := Prune(expected.RenderedProcessNames)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
|
||||
+45
-9
@@ -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}
|
||||
}
|
||||
|
||||
+36
-6
@@ -13,8 +13,38 @@ 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.
|
||||
func Prune(nodes report.Nodes) report.Nodes {
|
||||
result := report.Nodes{}
|
||||
for id, node := range nodes {
|
||||
result[id] = PruneNode(node)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// PruneNode returns a copy of the Node with all information not strictly
|
||||
// necessary for rendering nodes and edges stripped away. Specifically, that
|
||||
// means cutting out parts of the Node.
|
||||
func PruneNode(node report.Node) report.Node {
|
||||
prunedChildren := report.MakeNodeSet()
|
||||
node.Children.ForEach(func(child report.Node) {
|
||||
prunedChildren = prunedChildren.Add(PruneNode(child))
|
||||
})
|
||||
return report.MakeNode(
|
||||
node.ID).
|
||||
WithTopology(node.Topology).
|
||||
WithAdjacent(node.Adjacency.Copy()...).
|
||||
WithChildren(prunedChildren)
|
||||
}
|
||||
|
||||
func TestReduceRender(t *testing.T) {
|
||||
renderer := render.Reduce([]render.Renderer{
|
||||
@@ -26,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)
|
||||
}
|
||||
@@ -43,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)
|
||||
}
|
||||
@@ -65,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))
|
||||
}
|
||||
@@ -87,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))
|
||||
}
|
||||
|
||||
+2
-2
@@ -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 := render.ContainerWithImageNameRenderer.Render(rpt).Prune()
|
||||
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]
|
||||
|
||||
@@ -214,18 +214,3 @@ func (n Node) Merge(other Node) Node {
|
||||
cp.Children = cp.Children.Merge(other.Children)
|
||||
return cp
|
||||
}
|
||||
|
||||
// Prune returns a copy of the Node with all information not strictly necessary
|
||||
// for rendering nodes and edges stripped away. Specifically, that means
|
||||
// cutting out parts of the Node.
|
||||
func (n Node) Prune() Node {
|
||||
prunedChildren := MakeNodeSet()
|
||||
n.Children.ForEach(func(child Node) {
|
||||
prunedChildren = prunedChildren.Add(child.Prune())
|
||||
})
|
||||
return MakeNode(
|
||||
n.ID).
|
||||
WithTopology(n.Topology).
|
||||
WithAdjacent(n.Adjacency.Copy()...).
|
||||
WithChildren(prunedChildren)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,22 @@ func (n NodeSet) Add(nodes ...Node) NodeSet {
|
||||
return NodeSet{result}
|
||||
}
|
||||
|
||||
// Delete deletes the nodes from the NodeSet by ID. Delete is the only valid
|
||||
// way to shrink a NodeSet. Delete returns the NodeSet to enable chaining.
|
||||
func (n NodeSet) Delete(ids ...string) NodeSet {
|
||||
if n.Size() == 0 {
|
||||
return n
|
||||
}
|
||||
result := n.psMap
|
||||
for _, id := range ids {
|
||||
result = result.Delete(id)
|
||||
}
|
||||
if result.Size() == 0 {
|
||||
return EmptyNodeSet
|
||||
}
|
||||
return NodeSet{result}
|
||||
}
|
||||
|
||||
// Merge combines the two NodeSets and returns a new result.
|
||||
func (n NodeSet) Merge(other NodeSet) NodeSet {
|
||||
nSize, otherSize := n.Size(), other.Size()
|
||||
|
||||
@@ -167,6 +167,63 @@ func BenchmarkNodeSetAdd(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeSetDelete(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
input report.NodeSet
|
||||
nodes []string
|
||||
want report.NodeSet
|
||||
}{
|
||||
{
|
||||
input: report.NodeSet{},
|
||||
nodes: []string{},
|
||||
want: report.NodeSet{},
|
||||
},
|
||||
{
|
||||
input: report.EmptyNodeSet,
|
||||
nodes: []string{},
|
||||
want: report.EmptyNodeSet,
|
||||
},
|
||||
{
|
||||
input: report.MakeNodeSet(report.MakeNode("a")),
|
||||
nodes: []string{},
|
||||
want: report.MakeNodeSet(report.MakeNode("a")),
|
||||
},
|
||||
{
|
||||
input: report.EmptyNodeSet,
|
||||
nodes: []string{"a"},
|
||||
want: report.EmptyNodeSet,
|
||||
},
|
||||
{
|
||||
input: report.MakeNodeSet(report.MakeNode("a")),
|
||||
nodes: []string{"a"},
|
||||
want: report.EmptyNodeSet,
|
||||
},
|
||||
{
|
||||
input: report.MakeNodeSet(report.MakeNode("b")),
|
||||
nodes: []string{"a", "b"},
|
||||
want: report.EmptyNodeSet,
|
||||
},
|
||||
{
|
||||
input: report.MakeNodeSet(report.MakeNode("a")),
|
||||
nodes: []string{"c", "b"},
|
||||
want: report.MakeNodeSet(report.MakeNode("a")),
|
||||
},
|
||||
{
|
||||
input: report.MakeNodeSet(report.MakeNode("a"), report.MakeNode("c")),
|
||||
nodes: []string{"a", "a", "a"},
|
||||
want: report.MakeNodeSet(report.MakeNode("c")),
|
||||
},
|
||||
} {
|
||||
originalLen := testcase.input.Size()
|
||||
if want, have := testcase.want, testcase.input.Delete(testcase.nodes...); !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("%v + %v: want %v, have %v", testcase.input, testcase.nodes, want, have)
|
||||
}
|
||||
if testcase.input.Size() != originalLen {
|
||||
t.Errorf("%v + %v: modified the original input!", testcase.input, testcase.nodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeSetMerge(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
input report.NodeSet
|
||||
|
||||
@@ -185,16 +185,6 @@ func (n Nodes) Merge(other Nodes) Nodes {
|
||||
return cp
|
||||
}
|
||||
|
||||
// Prune returns a copy of the Nodes with all information not strictly
|
||||
// necessary for rendering nodes and edges in the UI cut away.
|
||||
func (n Nodes) Prune() Nodes {
|
||||
result := Nodes{}
|
||||
for id, node := range n {
|
||||
result[id] = node.Prune()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Validate checks the topology for various inconsistencies.
|
||||
func (t Topology) Validate() error {
|
||||
errs := []string{}
|
||||
|
||||
@@ -75,9 +75,13 @@ var (
|
||||
ClientContainerID = "a1b2c3d4e5"
|
||||
ClientContainerName = "client"
|
||||
ServerContainerID = "5e4d3c2b1a"
|
||||
ServerContainerName = "task-name-5-server-aceb93e2f2b797caba01"
|
||||
ClientContainerNodeID = report.MakeContainerNodeID(ClientContainerID)
|
||||
ServerContainerNodeID = report.MakeContainerNodeID(ServerContainerID)
|
||||
|
||||
ClientContainerHostname = ClientContainerName + ".hostname.com"
|
||||
ServerContainerHostname = ServerContainerName + ".hostname.com"
|
||||
|
||||
ClientContainerImageID = "imageid123"
|
||||
ServerContainerImageID = "imageid456"
|
||||
ClientContainerImageNodeID = report.MakeContainerImageNodeID(ClientContainerImageID)
|
||||
@@ -256,6 +260,7 @@ var (
|
||||
ClientContainerNodeID, map[string]string{
|
||||
docker.ContainerID: ClientContainerID,
|
||||
docker.ContainerName: ClientContainerName,
|
||||
docker.ContainerHostname: ClientContainerHostname,
|
||||
docker.ImageID: ClientContainerImageID,
|
||||
report.HostNodeID: ClientHostNodeID,
|
||||
docker.LabelPrefix + "io.kubernetes.pod.name": ClientPodID,
|
||||
@@ -276,7 +281,8 @@ var (
|
||||
|
||||
ServerContainerNodeID, map[string]string{
|
||||
docker.ContainerID: ServerContainerID,
|
||||
docker.ContainerName: "task-name-5-server-aceb93e2f2b797caba01",
|
||||
docker.ContainerName: ServerContainerName,
|
||||
docker.ContainerHostname: ServerContainerHostname,
|
||||
docker.ContainerState: docker.StateRunning,
|
||||
docker.ContainerStateHuman: docker.StateRunning,
|
||||
docker.ImageID: ServerContainerImageID,
|
||||
|
||||
Reference in New Issue
Block a user