mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-16 03:49:52 +00:00
Filter system containers from topologies; add API to control filters.
This commit is contained in:
committed by
Tom Wilkie
parent
6a6622e398
commit
1dfc725706
@@ -9,10 +9,18 @@ import (
|
||||
|
||||
// APITopologyDesc is returned in a list by the /api/topology handler.
|
||||
type APITopologyDesc struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"`
|
||||
Stats *topologyStats `json:"stats,omitempty"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"`
|
||||
Options map[string][]APITopologyOption `json:"options"`
|
||||
Stats *topologyStats `json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
// APITopologyOption describes a ¶m=value to a given topology.
|
||||
type APITopologyOption struct {
|
||||
Value string `json:"value"`
|
||||
Display string `json:"display"`
|
||||
Default bool `json:"default,omitempty"`
|
||||
}
|
||||
|
||||
type topologyStats struct {
|
||||
@@ -29,23 +37,30 @@ func makeTopologyList(rep xfer.Reporter) func(w http.ResponseWriter, r *http.Req
|
||||
topologies = []APITopologyDesc{}
|
||||
)
|
||||
for name, def := range topologyRegistry {
|
||||
// Don't show sub-topologies at the top level.
|
||||
if def.parent != "" {
|
||||
continue // subtopology, don't show at top level
|
||||
continue
|
||||
}
|
||||
|
||||
// Collect all sub-topologies of this one, depth=1 only.
|
||||
subTopologies := []APITopologyDesc{}
|
||||
for subName, subDef := range topologyRegistry {
|
||||
if subDef.parent == name {
|
||||
subTopologies = append(subTopologies, APITopologyDesc{
|
||||
Name: subDef.human,
|
||||
URL: "/api/topology/" + subName,
|
||||
Stats: stats(subDef.renderer.Render(rpt)),
|
||||
Name: subDef.human,
|
||||
URL: "/api/topology/" + subName,
|
||||
Options: makeTopologyOptions(subDef),
|
||||
Stats: stats(subDef.renderer.Render(rpt)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Append.
|
||||
topologies = append(topologies, APITopologyDesc{
|
||||
Name: def.human,
|
||||
URL: "/api/topology/" + name,
|
||||
SubTopologies: subTopologies,
|
||||
Options: makeTopologyOptions(def),
|
||||
Stats: stats(def.renderer.Render(rpt)),
|
||||
})
|
||||
}
|
||||
@@ -53,6 +68,20 @@ func makeTopologyList(rep xfer.Reporter) func(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
func makeTopologyOptions(view topologyView) map[string][]APITopologyOption {
|
||||
options := map[string][]APITopologyOption{}
|
||||
for param, optionVals := range view.options {
|
||||
for _, optionVal := range optionVals {
|
||||
options[param] = append(options[param], APITopologyOption{
|
||||
Value: optionVal.value,
|
||||
Display: optionVal.human,
|
||||
Default: optionVal.def,
|
||||
})
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func stats(r render.RenderableNodes) *topologyStats {
|
||||
var (
|
||||
nodes int
|
||||
|
||||
@@ -110,6 +110,14 @@ func captureTopology(rep xfer.Reporter, f func(xfer.Reporter, topologyView, http
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
for param, opts := range topology.options {
|
||||
value := r.FormValue(param)
|
||||
for _, opt := range opts {
|
||||
if (value == "" && opt.def) || (opt.value != "" && opt.value == value) {
|
||||
topology.renderer = opt.decorator(topology.renderer)
|
||||
}
|
||||
}
|
||||
}
|
||||
f(rep, topology, w, r)
|
||||
}
|
||||
}
|
||||
@@ -138,11 +146,19 @@ var topologyRegistry = map[string]topologyView{
|
||||
human: "Containers",
|
||||
parent: "",
|
||||
renderer: render.ContainerWithImageNameRenderer{},
|
||||
options: optionParams{"system": {
|
||||
{"show", "Show system containers", false, nop},
|
||||
{"hide", "Hide system containers", true, render.FilterSystem},
|
||||
}},
|
||||
},
|
||||
"containers-by-image": {
|
||||
human: "by image",
|
||||
parent: "containers",
|
||||
renderer: render.ContainerImageRenderer,
|
||||
options: optionParams{"system": {
|
||||
{"show", "Show system containers", false, nop},
|
||||
{"hide", "Hide system containers", true, render.FilterSystem},
|
||||
}},
|
||||
},
|
||||
"hosts": {
|
||||
human: "Hosts",
|
||||
@@ -155,4 +171,16 @@ type topologyView struct {
|
||||
human string
|
||||
parent string
|
||||
renderer render.Renderer
|
||||
options optionParams
|
||||
}
|
||||
|
||||
type optionParams map[string][]optionValue // param: values
|
||||
|
||||
type optionValue struct {
|
||||
value string // "hide"
|
||||
human string // "Hide system containers"
|
||||
def bool
|
||||
decorator func(render.Renderer) render.Renderer
|
||||
}
|
||||
|
||||
func nop(r render.Renderer) render.Renderer { return r }
|
||||
|
||||
4562
app/static.go
4562
app/static.go
File diff suppressed because it is too large
Load Diff
142
render/render.go
142
render/render.go
@@ -1,6 +1,9 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/weaveworks/scope/probe/docker"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
@@ -124,7 +127,8 @@ func (m Map) EdgeMetadata(rpt report.Report, srcRenderableID, dstRenderableID st
|
||||
}
|
||||
|
||||
// CustomRenderer allow for mapping functions that recived the entire topology
|
||||
// in one call - useful for functions that need to consider the entire graph
|
||||
// in one call - useful for functions that need to consider the entire graph.
|
||||
// We should minimise the use of this renderer type, as it is very inflexible.
|
||||
type CustomRenderer struct {
|
||||
RenderFunc func(RenderableNodes) RenderableNodes
|
||||
Renderer
|
||||
@@ -135,68 +139,116 @@ func (c CustomRenderer) Render(rpt report.Report) RenderableNodes {
|
||||
return c.RenderFunc(c.Renderer.Render(rpt))
|
||||
}
|
||||
|
||||
// IsConnected is the key added to Node.Metadata by ColorConnected
|
||||
// to indicate a node has an edge pointing to it or from it
|
||||
const IsConnected = "is_connected"
|
||||
|
||||
// OnlyConnected filters out unconnected RenderedNodes
|
||||
func OnlyConnected(input RenderableNodes) RenderableNodes {
|
||||
output := RenderableNodes{}
|
||||
for id, node := range ColorConnected(input) {
|
||||
if _, ok := node.Node.Metadata[IsConnected]; ok {
|
||||
output[id] = node
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// FilterUnconnected produces a renderer that filters unconnected nodes
|
||||
// from the given renderer
|
||||
func FilterUnconnected(r Renderer) Renderer {
|
||||
return CustomRenderer{
|
||||
RenderFunc: OnlyConnected,
|
||||
Renderer: r,
|
||||
}
|
||||
}
|
||||
|
||||
// ColorConnected colors nodes with the IsConnected key if
|
||||
// they have edges to or from them.
|
||||
func ColorConnected(input RenderableNodes) RenderableNodes {
|
||||
connected := map[string]struct{}{}
|
||||
void := struct{}{}
|
||||
func ColorConnected(r Renderer) Renderer {
|
||||
return CustomRenderer{
|
||||
Renderer: r,
|
||||
RenderFunc: func(input RenderableNodes) RenderableNodes {
|
||||
connected := map[string]struct{}{}
|
||||
void := struct{}{}
|
||||
|
||||
for id, node := range input {
|
||||
if len(node.Adjacency) == 0 {
|
||||
continue
|
||||
}
|
||||
for id, node := range input {
|
||||
if len(node.Adjacency) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
connected[id] = void
|
||||
for _, id := range node.Adjacency {
|
||||
connected[id] = void
|
||||
}
|
||||
connected[id] = void
|
||||
for _, id := range node.Adjacency {
|
||||
connected[id] = void
|
||||
}
|
||||
}
|
||||
|
||||
for id := range connected {
|
||||
node := input[id]
|
||||
node.Metadata[IsConnected] = "true"
|
||||
input[id] = node
|
||||
}
|
||||
return input
|
||||
},
|
||||
}
|
||||
|
||||
for id := range connected {
|
||||
node := input[id]
|
||||
node.Node.Metadata[IsConnected] = "true"
|
||||
input[id] = node
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
// Filter removes nodes from a view based on a predicate.
|
||||
type Filter struct {
|
||||
Renderer
|
||||
f func(RenderableNode) bool
|
||||
FilterFunc func(RenderableNode) bool
|
||||
}
|
||||
|
||||
// Render implements Renderer
|
||||
func (f Filter) Render(rpt report.Report) RenderableNodes {
|
||||
output := RenderableNodes{}
|
||||
for id, node := range f.Renderer.Render(rpt) {
|
||||
if f.f(node) {
|
||||
if f.FilterFunc(node) {
|
||||
output[id] = node
|
||||
}
|
||||
}
|
||||
|
||||
// Deleted nodes also need to be cut as destinations in adjacency lists.
|
||||
for id, node := range output {
|
||||
newAdjacency := make(report.IDList, 0, len(node.Adjacency))
|
||||
for _, dstID := range node.Adjacency {
|
||||
if _, ok := output[dstID]; ok {
|
||||
newAdjacency = newAdjacency.Add(dstID)
|
||||
}
|
||||
}
|
||||
node.Adjacency = newAdjacency
|
||||
output[id] = node
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// IsConnected is the key added to Node.Metadata by ColorConnected
|
||||
// to indicate a node has an edge pointing to it or from it
|
||||
const IsConnected = "is_connected"
|
||||
|
||||
// FilterUnconnected produces a renderer that filters unconnected nodes
|
||||
// from the given renderer
|
||||
func FilterUnconnected(r Renderer) Renderer {
|
||||
return Filter{
|
||||
Renderer: ColorConnected(r),
|
||||
FilterFunc: func(node RenderableNode) bool {
|
||||
_, ok := node.Metadata[IsConnected]
|
||||
return ok
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// FilterSystem is a Renderer which filters out system nodes.
|
||||
func FilterSystem(r Renderer) Renderer {
|
||||
return Filter{
|
||||
Renderer: r,
|
||||
FilterFunc: func(node RenderableNode) bool {
|
||||
containerName := node.Metadata[docker.ContainerName]
|
||||
if _, ok := systemContainerNames[containerName]; ok {
|
||||
return false
|
||||
}
|
||||
imagePrefix := strings.SplitN(node.Metadata[docker.ImageName], ":", 2)[0] // :(
|
||||
if _, ok := systemImagePrefixes[imagePrefix]; ok {
|
||||
return false
|
||||
}
|
||||
if node.Metadata[docker.LabelPrefix+"works.weave.role"] == "system" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var systemContainerNames = map[string]struct{}{
|
||||
"weavescope": {},
|
||||
"weavedns": {},
|
||||
"weave": {},
|
||||
"weaveproxy": {},
|
||||
"weaveexec": {},
|
||||
"ecs-agent": {},
|
||||
}
|
||||
|
||||
var systemImagePrefixes = map[string]struct{}{
|
||||
"weaveworks/scope": {},
|
||||
"weaveworks/weavedns": {},
|
||||
"weaveworks/weave": {},
|
||||
"weaveworks/weaveproxy": {},
|
||||
"weaveworks/weaveexec": {},
|
||||
"amazon/amazon-ecs-agent": {},
|
||||
}
|
||||
|
||||
@@ -187,7 +187,29 @@ func TestFilterRender(t *testing.T) {
|
||||
}
|
||||
have := expected.Sterilize(renderer.Render(report.MakeReport()))
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("want %+v, have %+v", want, have)
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterRender2(t *testing.T) {
|
||||
// Test adjacencies are removed for filtered nodes.
|
||||
renderer := render.Filter{
|
||||
FilterFunc: func(node render.RenderableNode) bool {
|
||||
return node.ID != "bar"
|
||||
},
|
||||
Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
|
||||
"foo": {ID: "foo", Node: report.MakeNode().WithAdjacent("bar")},
|
||||
"bar": {ID: "bar", Node: report.MakeNode().WithAdjacent("foo")},
|
||||
"baz": {ID: "baz", Node: report.MakeNode()},
|
||||
}},
|
||||
}
|
||||
want := render.RenderableNodes{
|
||||
"foo": {ID: "foo", Node: report.MakeNode()},
|
||||
"baz": {ID: "baz", Node: report.MakeNode()},
|
||||
}
|
||||
have := expected.Sterilize(renderer.Render(report.MakeReport()))
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,15 @@ func (rn RenderableNode) Copy() RenderableNode {
|
||||
// RenderableNodes is a set of RenderableNodes
|
||||
type RenderableNodes map[string]RenderableNode
|
||||
|
||||
// Copy produces a deep copy of the RenderableNodes
|
||||
func (rns RenderableNodes) Copy() RenderableNodes {
|
||||
result := RenderableNodes{}
|
||||
for key, value := range rns {
|
||||
result[key] = value.Copy()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Merge merges two sets of RenderableNodes, returning a new set.
|
||||
func (rns RenderableNodes) Merge(other RenderableNodes) RenderableNodes {
|
||||
result := RenderableNodes{}
|
||||
|
||||
@@ -85,15 +85,12 @@ var ContainerRenderer = MakeReduce(
|
||||
// but we need to be careful to ensure we only include each edge once, by only
|
||||
// including the ProcessRenderer once.
|
||||
Renderer: Filter{
|
||||
f: func(n RenderableNode) bool {
|
||||
FilterFunc: func(n RenderableNode) bool {
|
||||
_, inContainer := n.Node.Metadata[docker.ContainerID]
|
||||
_, isConnected := n.Node.Metadata[IsConnected]
|
||||
return inContainer || isConnected
|
||||
},
|
||||
Renderer: CustomRenderer{
|
||||
RenderFunc: ColorConnected,
|
||||
Renderer: ProcessRenderer,
|
||||
},
|
||||
Renderer: ColorConnected(ProcessRenderer),
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/probe/docker"
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/render/expected"
|
||||
"github.com/weaveworks/scope/test"
|
||||
@@ -33,6 +34,19 @@ func TestContainerRenderer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerFilterRenderer(t *testing.T) {
|
||||
// tag on of the containers in the topology and ensure
|
||||
// it is filtered out correctly.
|
||||
input := test.Report.Copy()
|
||||
input.Container.Nodes[test.ClientContainerNodeID].Metadata[docker.LabelPrefix+"works.weave.role"] = "system"
|
||||
have := expected.Sterilize(render.FilterSystem(render.ContainerWithImageNameRenderer{}).Render(input))
|
||||
want := expected.RenderedContainers.Copy()
|
||||
delete(want, test.ClientContainerID)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error(test.Diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerImageRenderer(t *testing.T) {
|
||||
have := expected.Sterilize(render.ContainerImageRenderer.Render(test.Report))
|
||||
want := expected.RenderedContainerImages
|
||||
|
||||
Reference in New Issue
Block a user