mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
The change is necessitated by the removal of procspied/ebpf endpoint filtering in the renderers, as a result of which the odd conntracked-only, unconnected pseudo node can sneak through. This new way of doing things also makes renderers more composable and robust, and more directly reflects the objective: - in the process topologies, filter out all unconnected nodes - in all other topologies, filter out unconnected pseudo nodes
359 lines
10 KiB
Go
359 lines
10 KiB
Go
package render
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/weaveworks/common/mtime"
|
|
"github.com/weaveworks/scope/probe/docker"
|
|
"github.com/weaveworks/scope/probe/kubernetes"
|
|
"github.com/weaveworks/scope/report"
|
|
)
|
|
|
|
const (
|
|
k8sNamespaceLabel = "io.kubernetes.pod.namespace"
|
|
swarmNamespaceLabel = "com.docker.stack.namespace"
|
|
)
|
|
|
|
// PreciousNodeRenderer ensures a node is never filtered out by decorators
|
|
type PreciousNodeRenderer struct {
|
|
PreciousNodeID string
|
|
Renderer
|
|
}
|
|
|
|
// Render implements Renderer
|
|
func (p PreciousNodeRenderer) Render(rpt report.Report, dct Decorator) report.Nodes {
|
|
undecoratedNodes := p.Renderer.Render(rpt, nil)
|
|
preciousNode, foundBeforeDecoration := undecoratedNodes[p.PreciousNodeID]
|
|
finalNodes := applyDecorator{ConstantRenderer(undecoratedNodes)}.Render(rpt, dct)
|
|
if _, ok := finalNodes[p.PreciousNodeID]; !ok && foundBeforeDecoration {
|
|
finalNodes[p.PreciousNodeID] = preciousNode
|
|
}
|
|
return finalNodes
|
|
}
|
|
|
|
// Stats implements Renderer
|
|
func (p PreciousNodeRenderer) Stats(rpt report.Report, dct Decorator) Stats {
|
|
// default to the underlying renderer
|
|
// TODO: we don't take into account the precious node, so we may be off by one
|
|
return p.Renderer.Stats(rpt, dct)
|
|
}
|
|
|
|
// CustomRenderer allow for mapping functions that received the entire topology
|
|
// 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(report.Nodes) report.Nodes
|
|
Renderer
|
|
}
|
|
|
|
// Render implements Renderer
|
|
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
|
|
// they have edges to or from them. Edges to/from yourself
|
|
// are not counted here (see #656).
|
|
func ColorConnected(r Renderer) Renderer {
|
|
return CustomRenderer{
|
|
Renderer: r,
|
|
RenderFunc: func(input report.Nodes) report.Nodes {
|
|
connected := map[string]struct{}{}
|
|
void := struct{}{}
|
|
|
|
for id, node := range input {
|
|
for _, adj := range node.Adjacency {
|
|
if adj != id {
|
|
connected[id] = void
|
|
connected[adj] = void
|
|
}
|
|
}
|
|
}
|
|
|
|
output := input.Copy()
|
|
for id := range connected {
|
|
output[id] = output[id].WithLatest(IsConnected, mtime.Now(), "true")
|
|
}
|
|
return output
|
|
},
|
|
}
|
|
}
|
|
|
|
// FilterFunc is the function type used by Filters
|
|
type FilterFunc func(report.Node) bool
|
|
|
|
// AnyFilterFunc checks if any of the filterfuncs matches.
|
|
func AnyFilterFunc(fs ...FilterFunc) FilterFunc {
|
|
return func(n report.Node) bool {
|
|
for _, f := range fs {
|
|
if f(n) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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 FilterFunc
|
|
}
|
|
|
|
// MakeFilter makes a new Filter (that ignores pseudo nodes).
|
|
func MakeFilter(f FilterFunc, r Renderer) Renderer {
|
|
return Memoise(&Filter{
|
|
Renderer: r,
|
|
FilterFunc: func(n report.Node) bool {
|
|
return n.Topology == Pseudo || f(n)
|
|
},
|
|
})
|
|
}
|
|
|
|
// MakeFilterPseudo makes a new Filter that will not ignore pseudo nodes.
|
|
func MakeFilterPseudo(f FilterFunc, r Renderer) Renderer {
|
|
return Memoise(&Filter{
|
|
Renderer: r,
|
|
FilterFunc: f,
|
|
})
|
|
}
|
|
|
|
// MakeFilterDecorator makes a decorator that filters out non-pseudo nodes
|
|
// which match the predicate.
|
|
func MakeFilterDecorator(f FilterFunc) Decorator {
|
|
return func(renderer Renderer) Renderer {
|
|
return MakeFilter(f, renderer)
|
|
}
|
|
}
|
|
|
|
// MakeFilterPseudoDecorator makes a decorator that filters out all nodes
|
|
// (including pseudo nodes) which match the predicate.
|
|
func MakeFilterPseudoDecorator(f FilterFunc) Decorator {
|
|
return func(renderer Renderer) Renderer {
|
|
return MakeFilterPseudo(f, renderer)
|
|
}
|
|
}
|
|
|
|
// Render implements Renderer
|
|
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, dct Decorator) (report.Nodes, int) {
|
|
output := report.Nodes{}
|
|
inDegrees := map[string]int{}
|
|
filtered := 0
|
|
for id, node := range f.Renderer.Render(rpt, dct) {
|
|
if f.FilterFunc(node) {
|
|
output[id] = node
|
|
inDegrees[id] = 0
|
|
} else {
|
|
filtered++
|
|
}
|
|
}
|
|
|
|
// Deleted nodes also need to be cut as destinations in adjacency lists.
|
|
for id, node := range output {
|
|
newAdjacency := report.MakeIDList()
|
|
for _, dstID := range node.Adjacency {
|
|
if _, ok := output[dstID]; ok {
|
|
newAdjacency = newAdjacency.Add(dstID)
|
|
inDegrees[dstID]++
|
|
}
|
|
}
|
|
node.Adjacency = newAdjacency
|
|
output[id] = node
|
|
}
|
|
|
|
// Remove unconnected pseudo nodes, see #483.
|
|
for id, inDegree := range inDegrees {
|
|
if inDegree > 0 {
|
|
continue
|
|
}
|
|
node := output[id]
|
|
if node.Topology != Pseudo || len(node.Adjacency) > 0 {
|
|
continue
|
|
}
|
|
delete(output, id)
|
|
filtered++
|
|
}
|
|
return output, filtered
|
|
}
|
|
|
|
// 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
|
|
// to indicate a node has an edge pointing to it or from it
|
|
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 FilterFunc) FilterFunc {
|
|
return func(node report.Node) bool { return !f(node) }
|
|
}
|
|
|
|
// FilterUnconnected produces a renderer that filters unconnected nodes
|
|
// from the given renderer
|
|
func FilterUnconnected(r Renderer) Renderer {
|
|
return MakeFilterPseudo(
|
|
func(node report.Node) bool {
|
|
_, ok := node.Latest.Lookup(IsConnected)
|
|
return ok
|
|
},
|
|
ColorConnected(r),
|
|
)
|
|
}
|
|
|
|
// FilterUnconnectedPseudo produces a renderer that filters
|
|
// unconnected pseudo nodes from the given renderer
|
|
func FilterUnconnectedPseudo(r Renderer) Renderer {
|
|
return MakeFilterPseudo(
|
|
func(node report.Node) bool {
|
|
if !IsPseudoTopology(node) {
|
|
return true
|
|
}
|
|
_, ok := node.Latest.Lookup(IsConnected)
|
|
return ok
|
|
},
|
|
ColorConnected(r),
|
|
)
|
|
}
|
|
|
|
// Noop allows all nodes through
|
|
func Noop(_ report.Node) bool { return true }
|
|
|
|
// IsRunning checks if the node is a running docker container
|
|
func IsRunning(n report.Node) bool {
|
|
state, ok := n.Latest.Lookup(docker.ContainerState)
|
|
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)
|
|
|
|
// 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
|
|
}
|
|
imageName, _ := n.Latest.Lookup(docker.ImageName)
|
|
imagePrefix := strings.SplitN(imageName, ":", 2)[0] // :(
|
|
if _, ok := systemImagePrefixes[imagePrefix]; ok || kubernetes.IsPauseImageName(imagePrefix) {
|
|
return false
|
|
}
|
|
roleLabel, _ := n.Latest.Lookup(docker.LabelPrefix + "works.weave.role")
|
|
if roleLabel == "system" {
|
|
return false
|
|
}
|
|
roleLabel, _ = n.Latest.Lookup(docker.ImageLabelPrefix + "works.weave.role")
|
|
if roleLabel == "system" {
|
|
return false
|
|
}
|
|
namespace, _ := n.Latest.Lookup(docker.LabelPrefix + k8sNamespaceLabel)
|
|
if namespace == "kube-system" {
|
|
return false
|
|
}
|
|
podName, _ := n.Latest.Lookup(docker.LabelPrefix + "io.kubernetes.pod.name")
|
|
if strings.HasPrefix(podName, "kube-system/") {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IsSystem checks if the node is a "system" node
|
|
var IsSystem = Complement(IsApplication)
|
|
|
|
// HasLabel checks if the node has the desired docker label
|
|
func HasLabel(labelKey string, labelValue string) FilterFunc {
|
|
return func(n report.Node) bool {
|
|
value, _ := n.Latest.Lookup(docker.LabelPrefix + labelKey)
|
|
if value == labelValue {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
// DoesNotHaveLabel checks if the node does NOT have the specified docker label
|
|
func DoesNotHaveLabel(labelKey string, labelValue string) FilterFunc {
|
|
return Complement(HasLabel(labelKey, labelValue))
|
|
}
|
|
|
|
// IsNotPseudo returns true if the node is not a pseudo node
|
|
// or internet/service nodes.
|
|
func IsNotPseudo(n report.Node) bool {
|
|
return n.Topology != Pseudo || strings.HasSuffix(n.ID, TheInternetID) || strings.HasPrefix(n.ID, ServiceNodeIDPrefix)
|
|
}
|
|
|
|
// IsNamespace checks if the node is a pod/service in the specified namespace
|
|
func IsNamespace(namespace string) FilterFunc {
|
|
return func(n report.Node) bool {
|
|
tryKeys := []string{kubernetes.Namespace, docker.LabelPrefix + k8sNamespaceLabel, docker.StackNamespace, docker.LabelPrefix + swarmNamespaceLabel}
|
|
gotNamespace := ""
|
|
for _, key := range tryKeys {
|
|
if value, ok := n.Latest.Lookup(key); ok {
|
|
gotNamespace = value
|
|
break
|
|
}
|
|
}
|
|
// Special case for docker
|
|
if namespace == docker.DefaultNamespace && gotNamespace == "" {
|
|
return true
|
|
}
|
|
return namespace == gotNamespace
|
|
}
|
|
}
|
|
|
|
// IsTopology checks if the node is from a particular report topology
|
|
func IsTopology(topology string) FilterFunc {
|
|
return func(n report.Node) bool {
|
|
return n.Topology == topology
|
|
}
|
|
}
|
|
|
|
// IsPseudoTopology returns true if the node is in a pseudo topology,
|
|
// mimicing the check performed by MakeFilter() instead of the more complex check in IsNotPseudo()
|
|
var IsPseudoTopology = IsTopology(Pseudo)
|
|
|
|
var systemContainerNames = map[string]struct{}{
|
|
"weavescope": {},
|
|
"weavedns": {},
|
|
"weave": {},
|
|
"weaveproxy": {},
|
|
"weaveexec": {},
|
|
"ecs-agent": {},
|
|
}
|
|
|
|
var systemImagePrefixes = map[string]struct{}{
|
|
"swarm": {},
|
|
"weaveworks/scope": {},
|
|
"weaveworks/weavedns": {},
|
|
"weaveworks/weave": {},
|
|
"weaveworks/weaveproxy": {},
|
|
"weaveworks/weaveexec": {},
|
|
"amazon/amazon-ecs-agent": {},
|
|
"openshift/origin-pod": {},
|
|
"docker.io/openshift/origin-pod": {},
|
|
}
|