Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
小 白蛋
Scope
Commits
1dfc7257
Commit
1dfc7257
authored
9 years ago
by
Peter Bourgon
Committed by
Tom Wilkie
9 years ago
Browse files
Options
Download
Email Patches
Plain Diff
Filter system containers from topologies; add API to control filters.
parent
6a6622e3
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
app/api_topologies.go
+37
-8
app/api_topologies.go
app/router.go
+28
-0
app/router.go
app/static.go
+2362
-2200
app/static.go
render/render.go
+98
-46
render/render.go
render/render_test.go
+23
-1
render/render_test.go
render/renderable_node.go
+9
-0
render/renderable_node.go
render/topologies.go
+2
-5
render/topologies.go
render/topologies_test.go
+14
-0
render/topologies_test.go
with
2573 additions
and
2260 deletions
+2573
-2260
app/api_topologies.go
+
37
-
8
View file @
1dfc7257
...
...
@@ -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
...
...
This diff is collapsed.
Click to expand it.
app/router.go
+
28
-
0
View file @
1dfc7257
...
...
@@ -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
}
This diff is collapsed.
Click to expand it.
app/static.go
+
2362
-
2200
View file @
1dfc7257
This diff is collapsed.
Click to expand it.
render/render.go
+
98
-
46
View file @
1dfc7257
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
{}{}
for
id
,
node
:=
range
input
{
if
len
(
node
.
Adjacency
)
==
0
{
continue
}
connected
[
id
]
=
void
for
_
,
id
:=
range
node
.
Adjacency
{
connected
[
id
]
=
void
}
}
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
}
connected
[
id
]
=
void
for
_
,
id
:=
range
node
.
Adjacency
{
connected
[
id
]
=
void
}
}
for
id
:=
range
connected
{
node
:=
input
[
id
]
node
.
Node
.
Metadata
[
IsConnected
]
=
"true"
input
[
id
]
=
node
for
id
:=
range
connected
{
node
:=
input
[
id
]
node
.
Metadata
[
IsConnected
]
=
"true"
input
[
id
]
=
node
}
return
input
},
}
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"
:
{},
}
This diff is collapsed.
Click to expand it.
render/render_test.go
+
23
-
1
View file @
1dfc7257
...
...
@@ -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
))
}
}
...
...
This diff is collapsed.
Click to expand it.
render/renderable_node.go
+
9
-
0
View file @
1dfc7257
...
...
@@ -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
{}
...
...
This diff is collapsed.
Click to expand it.
render/topologies.go
+
2
-
5
View file @
1dfc7257
...
...
@@ -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
),
},
},
...
...
This diff is collapsed.
Click to expand it.
render/topologies_test.go
+
14
-
0
View file @
1dfc7257
...
...
@@ -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
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment