mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-21 22:36:39 +00:00
Move mapping functions and main render function in render package.
This commit is contained in:
@@ -50,30 +50,30 @@ var topologyRegistry = map[string]topologyView{
|
||||
"applications": {
|
||||
human: "Applications",
|
||||
parent: "",
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: report.ProcessPID, Pseudo: report.GenericPseudoNode},
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessPID, Pseudo: render.GenericPseudoNode},
|
||||
},
|
||||
"applications-by-name": {
|
||||
human: "by name",
|
||||
parent: "applications",
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: report.ProcessName, Pseudo: report.GenericGroupedPseudoNode},
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessName, Pseudo: render.GenericGroupedPseudoNode},
|
||||
},
|
||||
"containers": {
|
||||
human: "Containers",
|
||||
parent: "",
|
||||
renderer: render.Reduce([]render.Renderer{
|
||||
render.Map{Selector: report.SelectEndpoint, Mapper: report.MapEndpoint2Container, Pseudo: report.InternetOnlyPseudoNode},
|
||||
render.Map{Selector: report.SelectContainer, Mapper: report.MapContainerIdentity, Pseudo: report.InternetOnlyPseudoNode},
|
||||
render.Map{Selector: report.SelectEndpoint, Mapper: render.MapEndpoint2Container, Pseudo: render.InternetOnlyPseudoNode},
|
||||
render.Map{Selector: report.SelectContainer, Mapper: render.MapContainerIdentity, Pseudo: render.InternetOnlyPseudoNode},
|
||||
}),
|
||||
},
|
||||
"containers-by-image": {
|
||||
human: "by image",
|
||||
parent: "containers",
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: report.ProcessContainerImage, Pseudo: report.InternetOnlyPseudoNode},
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessContainerImage, Pseudo: render.InternetOnlyPseudoNode},
|
||||
},
|
||||
"hosts": {
|
||||
human: "Hosts",
|
||||
parent: "",
|
||||
renderer: render.Map{Selector: report.SelectAddress, Mapper: report.NetworkHostname, Pseudo: report.GenericPseudoNode},
|
||||
renderer: render.Map{Selector: report.SelectAddress, Mapper: render.NetworkHostname, Pseudo: render.GenericPseudoNode},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,16 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
func handleTXT(r Reporter) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
dot(w, r.Report().Endpoint.RenderBy(mapFunc(req), nil))
|
||||
|
||||
renderer := render.Map{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil}
|
||||
dot(w, renderer.Render(r.Report()))
|
||||
|
||||
//report.Render(r.Report(), report.SelectEndpoint, mapFunc(req), report.NoPseudoNode))
|
||||
}
|
||||
@@ -32,7 +35,8 @@ func handleSVG(r Reporter) http.HandlerFunc {
|
||||
|
||||
cmd.Stdout = w
|
||||
|
||||
dot(wc, r.Report().Endpoint.RenderBy(mapFunc(req), nil))
|
||||
renderer := render.Map{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil}
|
||||
dot(wc, renderer.Render(r.Report()))
|
||||
wc.Close()
|
||||
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
@@ -98,12 +102,12 @@ func engine(r *http.Request) string {
|
||||
return engine
|
||||
}
|
||||
|
||||
func mapFunc(r *http.Request) report.MapFunc {
|
||||
func mapFunc(r *http.Request) render.MapFunc {
|
||||
switch strings.ToLower(r.FormValue("map_func")) {
|
||||
case "hosts", "networkhost", "networkhostname":
|
||||
return report.NetworkHostname
|
||||
return render.NetworkHostname
|
||||
}
|
||||
return report.ProcessPID
|
||||
return render.ProcessPID
|
||||
}
|
||||
|
||||
func classView(r *http.Request) bool {
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
package report
|
||||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
const humanTheInternet = "the Internet"
|
||||
|
||||
func newRenderableNode(id, major, minor, rank string) RenderableNode {
|
||||
return RenderableNode{
|
||||
func newRenderableNode(id, major, minor, rank string) report.RenderableNode {
|
||||
return report.RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: minor,
|
||||
Rank: rank,
|
||||
Pseudo: false,
|
||||
Metadata: AggregateMetadata{},
|
||||
Metadata: report.AggregateMetadata{},
|
||||
}
|
||||
}
|
||||
|
||||
func newPseudoNode(id, major, minor string) RenderableNode {
|
||||
return RenderableNode{
|
||||
func newPseudoNode(id, major, minor string) report.RenderableNode {
|
||||
return report.RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: minor,
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
Metadata: report.AggregateMetadata{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,36 +41,18 @@ func newPseudoNode(id, major, minor string) RenderableNode {
|
||||
//
|
||||
// If the final output parameter is false, the node shall be omitted from the
|
||||
// rendered topology.
|
||||
type MapFunc func(NodeMetadata) (RenderableNode, bool)
|
||||
type MapFunc func(report.NodeMetadata) (report.RenderableNode, bool)
|
||||
|
||||
// PseudoFunc creates RenderableNode representing pseudo nodes given the dstNodeID.
|
||||
// The srcNode renderable node is essentially from MapFunc, representing one of
|
||||
// the rendered nodes this pseudo node refers to. srcNodeID and dstNodeID are
|
||||
// node IDs prior to mapping.
|
||||
type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string) (RenderableNode, bool)
|
||||
|
||||
// TopologySelector selects a single topology from a report.
|
||||
type TopologySelector func(r Report) Topology
|
||||
|
||||
// SelectEndpoint selects the endpoint topology.
|
||||
func SelectEndpoint(r Report) Topology {
|
||||
return r.Endpoint
|
||||
}
|
||||
|
||||
// SelectAddress selects the address topology.
|
||||
func SelectAddress(r Report) Topology {
|
||||
return r.Address
|
||||
}
|
||||
|
||||
// SelectContainer selects the container topology.
|
||||
func SelectContainer(r Report) Topology {
|
||||
return r.Container
|
||||
}
|
||||
type PseudoFunc func(srcNodeID string, srcNode report.RenderableNode, dstNodeID string) (report.RenderableNode, bool)
|
||||
|
||||
// ProcessPID takes a node NodeMetadata from topology, and returns a
|
||||
// representation with the ID based on the process PID and the labels based on
|
||||
// the process name.
|
||||
func ProcessPID(m NodeMetadata) (RenderableNode, bool) {
|
||||
func ProcessPID(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
var (
|
||||
identifier = fmt.Sprintf("%s:%s:%s", "pid", m["domain"], m["pid"])
|
||||
minor = fmt.Sprintf("%s (%s)", m["domain"], m["pid"])
|
||||
@@ -81,7 +65,7 @@ func ProcessPID(m NodeMetadata) (RenderableNode, bool) {
|
||||
// ProcessName takes a node NodeMetadata from a topology, and returns a
|
||||
// representation with the ID based on the process name (grouping all
|
||||
// processes with the same name together).
|
||||
func ProcessName(m NodeMetadata) (RenderableNode, bool) {
|
||||
func ProcessName(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
show := m["pid"] != "" && m["name"] != ""
|
||||
return newRenderableNode(m["name"], m["name"], "", m["name"]), show
|
||||
}
|
||||
@@ -90,7 +74,7 @@ func ProcessName(m NodeMetadata) (RenderableNode, bool) {
|
||||
// in. We consider container and image IDs to be globally unique, and so don't
|
||||
// scope them further by e.g. host. If no container metadata is found, nodes are
|
||||
// grouped into the Uncontained node.
|
||||
func MapEndpoint2Container(m NodeMetadata) (RenderableNode, bool) {
|
||||
func MapEndpoint2Container(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_container_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
@@ -102,7 +86,7 @@ func MapEndpoint2Container(m NodeMetadata) (RenderableNode, bool) {
|
||||
}
|
||||
|
||||
// MapContainerIdentity maps container topology node to container mapped nodes.
|
||||
func MapContainerIdentity(m NodeMetadata) (RenderableNode, bool) {
|
||||
func MapContainerIdentity(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_container_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
@@ -116,7 +100,7 @@ func MapContainerIdentity(m NodeMetadata) (RenderableNode, bool) {
|
||||
// ProcessContainerImage maps topology nodes to the container images they run
|
||||
// on. If no container metadata is found, nodes are grouped into the
|
||||
// Uncontained node.
|
||||
func ProcessContainerImage(m NodeMetadata) (RenderableNode, bool) {
|
||||
func ProcessContainerImage(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_image_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
@@ -130,7 +114,7 @@ func ProcessContainerImage(m NodeMetadata) (RenderableNode, bool) {
|
||||
// NetworkHostname takes a node NodeMetadata and returns a representation
|
||||
// based on the hostname. Major label is the hostname, the minor label is the
|
||||
// domain, if any.
|
||||
func NetworkHostname(m NodeMetadata) (RenderableNode, bool) {
|
||||
func NetworkHostname(m report.NodeMetadata) (report.RenderableNode, bool) {
|
||||
var (
|
||||
name = m["name"]
|
||||
domain = ""
|
||||
@@ -146,10 +130,10 @@ func NetworkHostname(m NodeMetadata) (RenderableNode, bool) {
|
||||
|
||||
// GenericPseudoNode contains heuristics for building sensible pseudo nodes.
|
||||
// It should go away.
|
||||
func GenericPseudoNode(src string, srcMapped RenderableNode, dst string) (RenderableNode, bool) {
|
||||
func GenericPseudoNode(src string, srcMapped report.RenderableNode, dst string) (report.RenderableNode, bool) {
|
||||
var maj, min, outputID string
|
||||
|
||||
if dst == TheInternet {
|
||||
if dst == report.TheInternet {
|
||||
outputID = dst
|
||||
maj, min = humanTheInternet, ""
|
||||
} else {
|
||||
@@ -158,7 +142,7 @@ func GenericPseudoNode(src string, srcMapped RenderableNode, dst string) (Render
|
||||
srcNodeAddr, srcNodePort := trySplitAddr(src)
|
||||
dstNodeAddr, _ := trySplitAddr(dst)
|
||||
|
||||
outputID = MakePseudoNodeID(dstNodeAddr, srcNodeAddr, srcNodePort)
|
||||
outputID = report.MakePseudoNodeID(dstNodeAddr, srcNodeAddr, srcNodePort)
|
||||
maj, min = dstNodeAddr, ""
|
||||
}
|
||||
|
||||
@@ -167,17 +151,17 @@ func GenericPseudoNode(src string, srcMapped RenderableNode, dst string) (Render
|
||||
|
||||
// GenericGroupedPseudoNode contains heuristics for building sensible pseudo nodes.
|
||||
// It should go away.
|
||||
func GenericGroupedPseudoNode(src string, srcMapped RenderableNode, dst string) (RenderableNode, bool) {
|
||||
func GenericGroupedPseudoNode(src string, srcMapped report.RenderableNode, dst string) (report.RenderableNode, bool) {
|
||||
var maj, min, outputID string
|
||||
|
||||
if dst == TheInternet {
|
||||
if dst == report.TheInternet {
|
||||
outputID = dst
|
||||
maj, min = humanTheInternet, ""
|
||||
} else {
|
||||
// When grouping, emit one pseudo node per (srcNodeAddress, dstNodeAddr)
|
||||
dstNodeAddr, _ := trySplitAddr(dst)
|
||||
|
||||
outputID = MakePseudoNodeID(dstNodeAddr, srcMapped.ID)
|
||||
outputID = report.MakePseudoNodeID(dstNodeAddr, srcMapped.ID)
|
||||
maj, min = dstNodeAddr, ""
|
||||
}
|
||||
|
||||
@@ -185,11 +169,11 @@ func GenericGroupedPseudoNode(src string, srcMapped RenderableNode, dst string)
|
||||
}
|
||||
|
||||
// InternetOnlyPseudoNode never creates a pseudo node, unless it's the Internet.
|
||||
func InternetOnlyPseudoNode(_ string, _ RenderableNode, dst string) (RenderableNode, bool) {
|
||||
if dst == TheInternet {
|
||||
return newPseudoNode(TheInternet, humanTheInternet, ""), true
|
||||
func InternetOnlyPseudoNode(_ string, _ report.RenderableNode, dst string) (report.RenderableNode, bool) {
|
||||
if dst == report.TheInternet {
|
||||
return newPseudoNode(report.TheInternet, humanTheInternet, ""), true
|
||||
}
|
||||
return RenderableNode{}, false
|
||||
return report.RenderableNode{}, false
|
||||
}
|
||||
|
||||
// trySplitAddr is basically ParseArbitraryNodeID, since its callsites
|
||||
@@ -199,7 +183,7 @@ func InternetOnlyPseudoNode(_ string, _ RenderableNode, dst string) (RenderableN
|
||||
//
|
||||
// TODO change how pseudofuncs work, and eliminate this helper.
|
||||
func trySplitAddr(addr string) (string, string) {
|
||||
fields := strings.SplitN(addr, ScopeDelim, 3)
|
||||
fields := strings.SplitN(addr, report.ScopeDelim, 3)
|
||||
if len(fields) == 3 {
|
||||
return fields[1], fields[2]
|
||||
}
|
||||
@@ -1,22 +1,24 @@
|
||||
package report
|
||||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
func TestUngroupedMapping(t *testing.T) {
|
||||
for i, c := range []struct {
|
||||
f MapFunc
|
||||
id string
|
||||
meta NodeMetadata
|
||||
meta report.NodeMetadata
|
||||
wantOK bool
|
||||
wantID, wantMajor, wantMinor, wantRank string
|
||||
}{
|
||||
{
|
||||
f: NetworkHostname,
|
||||
id: MakeAddressNodeID("", "1.2.3.4"),
|
||||
meta: NodeMetadata{
|
||||
id: report.MakeAddressNodeID("", "1.2.3.4"),
|
||||
meta: report.NodeMetadata{
|
||||
"name": "my.host",
|
||||
},
|
||||
wantOK: true,
|
||||
@@ -27,8 +29,8 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
},
|
||||
{
|
||||
f: NetworkHostname,
|
||||
id: MakeAddressNodeID("", "1.2.3.4"),
|
||||
meta: NodeMetadata{
|
||||
id: report.MakeAddressNodeID("", "1.2.3.4"),
|
||||
meta: report.NodeMetadata{
|
||||
"name": "localhost",
|
||||
},
|
||||
wantOK: true,
|
||||
@@ -40,7 +42,7 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
{
|
||||
f: ProcessPID,
|
||||
id: "not-used-beta",
|
||||
meta: NodeMetadata{
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
"name": "curl",
|
||||
"domain": "hosta",
|
||||
@@ -54,7 +56,7 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
{
|
||||
f: MapEndpoint2Container,
|
||||
id: "foo-id",
|
||||
meta: NodeMetadata{
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
"name": "curl",
|
||||
"domain": "hosta",
|
||||
@@ -68,7 +70,7 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
{
|
||||
f: MapEndpoint2Container,
|
||||
id: "bar-id",
|
||||
meta: NodeMetadata{
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
"name": "curl",
|
||||
"domain": "hosta",
|
||||
114
render/render.go
114
render/render.go
@@ -1,6 +1,8 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
@@ -36,16 +38,120 @@ func (r Reduce) AggregateMetadata(rpt report.Report, localID, remoteID string) r
|
||||
// Mapper functions and topology selector.
|
||||
type Map struct {
|
||||
Selector report.TopologySelector
|
||||
Mapper report.MapFunc
|
||||
Pseudo report.PseudoFunc
|
||||
Mapper MapFunc
|
||||
Pseudo PseudoFunc
|
||||
}
|
||||
|
||||
// Render produces a set of RenderableNodes given a Report
|
||||
func (m Map) Render(rpt report.Report) report.RenderableNodes {
|
||||
return m.Selector(rpt).RenderBy(m.Mapper, m.Pseudo)
|
||||
return renderTopology(m.Selector(rpt), m.Mapper, m.Pseudo)
|
||||
}
|
||||
|
||||
// RenderBy transforms a given Topology into a set of RenderableNodes, which
|
||||
// the UI will render collectively as a graph. Note that a RenderableNode will
|
||||
// always be rendered with other nodes, and therefore contains limited detail.
|
||||
//
|
||||
// RenderBy takes a a MapFunc, which defines how to group and label nodes. Npdes
|
||||
// with the same mapped IDs will be merged.
|
||||
func renderTopology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) report.RenderableNodes {
|
||||
nodes := report.RenderableNodes{}
|
||||
|
||||
// Build a set of RenderableNodes for all non-pseudo probes, and an
|
||||
// addressID to nodeID lookup map. Multiple addressIDs can map to the same
|
||||
// RenderableNodes.
|
||||
var (
|
||||
source2mapped = map[string]string{} // source node ID -> mapped node ID
|
||||
source2host = map[string]string{} // source node ID -> origin host ID
|
||||
)
|
||||
for nodeID, metadata := range t.NodeMetadatas {
|
||||
mapped, ok := mapFunc(metadata)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// mapped.ID needs not be unique over all addressIDs. If not, we merge with
|
||||
// the existing data, on the assumption that the MapFunc returns the same
|
||||
// data.
|
||||
existing, ok := nodes[mapped.ID]
|
||||
if ok {
|
||||
mapped.Merge(existing)
|
||||
}
|
||||
|
||||
mapped.Origins = mapped.Origins.Add(nodeID)
|
||||
nodes[mapped.ID] = mapped
|
||||
source2mapped[nodeID] = mapped.ID
|
||||
source2host[nodeID] = metadata[report.HostNodeID]
|
||||
}
|
||||
|
||||
// Walk the graph and make connections.
|
||||
for src, dsts := range t.Adjacency {
|
||||
var (
|
||||
srcNodeID, ok = report.ParseAdjacencyID(src)
|
||||
//srcOriginHostID, _, ok2 = ParseNodeID(srcNodeID)
|
||||
srcHostNodeID = source2host[srcNodeID]
|
||||
srcRenderableID = source2mapped[srcNodeID] // must exist
|
||||
srcRenderableNode = nodes[srcRenderableID] // must exist
|
||||
)
|
||||
if !ok {
|
||||
log.Printf("bad adjacency ID %q", src)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, dstNodeID := range dsts {
|
||||
dstRenderableID, ok := source2mapped[dstNodeID]
|
||||
if !ok {
|
||||
pseudoNode, ok := pseudoFunc(srcNodeID, srcRenderableNode, dstNodeID)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
dstRenderableID = pseudoNode.ID
|
||||
nodes[dstRenderableID] = pseudoNode
|
||||
source2mapped[dstNodeID] = dstRenderableID
|
||||
}
|
||||
|
||||
srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID)
|
||||
srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcHostNodeID)
|
||||
srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcNodeID)
|
||||
edgeID := report.MakeEdgeID(srcNodeID, dstNodeID)
|
||||
if md, ok := t.EdgeMetadatas[edgeID]; ok {
|
||||
srcRenderableNode.Metadata.Merge(md.Transform())
|
||||
}
|
||||
}
|
||||
|
||||
nodes[srcRenderableID] = srcRenderableNode
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
// AggregateMetadata produces an AggregateMetadata for a given edge
|
||||
func (m Map) AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata {
|
||||
return m.Selector(rpt).EdgeMetadata(m.Mapper, localID, remoteID).Transform()
|
||||
return edgeMetadata(m.Selector(rpt), m.Mapper, localID, remoteID).Transform()
|
||||
}
|
||||
|
||||
// EdgeMetadata gives the metadata of an edge from the perspective of the
|
||||
// srcRenderableID. Since an edgeID can have multiple edges on the address
|
||||
// level, it uses the supplied mapping function to translate address IDs to
|
||||
// renderable node (mapped) IDs.
|
||||
func edgeMetadata(t report.Topology, mapFunc MapFunc, srcRenderableID, dstRenderableID string) report.EdgeMetadata {
|
||||
metadata := report.EdgeMetadata{}
|
||||
for edgeID, edgeMeta := range t.EdgeMetadatas {
|
||||
src, dst, ok := report.ParseEdgeID(edgeID)
|
||||
if !ok {
|
||||
log.Printf("bad edge ID %q", edgeID)
|
||||
continue
|
||||
}
|
||||
if src != report.TheInternet {
|
||||
mapped, _ := mapFunc(t.NodeMetadatas[src])
|
||||
src = mapped.ID
|
||||
}
|
||||
if dst != report.TheInternet {
|
||||
mapped, _ := mapFunc(t.NodeMetadatas[dst])
|
||||
dst = mapped.ID
|
||||
}
|
||||
if src == srcRenderableID && dst == dstRenderableID {
|
||||
metadata.Flatten(edgeMeta)
|
||||
}
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package render_test
|
||||
package render
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
)
|
||||
|
||||
func init() {
|
||||
spew.Config.SortKeys = true // :\
|
||||
}
|
||||
|
||||
type mockRenderer struct {
|
||||
report.RenderableNodes
|
||||
aggregateMetadata report.AggregateMetadata
|
||||
@@ -21,7 +27,7 @@ func (m mockRenderer) AggregateMetadata(rpt report.Report, localID, remoteID str
|
||||
}
|
||||
|
||||
func TestReduceRender(t *testing.T) {
|
||||
renderer := render.Reduce([]render.Renderer{
|
||||
renderer := Reduce([]Renderer{
|
||||
mockRenderer{RenderableNodes: report.RenderableNodes{"foo": {ID: "foo"}}},
|
||||
mockRenderer{RenderableNodes: report.RenderableNodes{"bar": {ID: "bar"}}},
|
||||
})
|
||||
@@ -35,7 +41,7 @@ func TestReduceRender(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReduceEdge(t *testing.T) {
|
||||
renderer := render.Reduce([]render.Renderer{
|
||||
renderer := Reduce([]Renderer{
|
||||
mockRenderer{aggregateMetadata: report.AggregateMetadata{"foo": 1}},
|
||||
mockRenderer{aggregateMetadata: report.AggregateMetadata{"bar": 2}},
|
||||
})
|
||||
@@ -47,3 +53,323 @@ func TestReduceEdge(t *testing.T) {
|
||||
t.Errorf("want %+v, have %+v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
clientHostID = "client.hostname.com"
|
||||
serverHostID = "server.hostname.com"
|
||||
randomHostID = "random.hostname.com"
|
||||
unknownHostID = ""
|
||||
|
||||
clientHostNodeID = report.MakeHostNodeID(clientHostID)
|
||||
serverHostNodeID = report.MakeHostNodeID(serverHostID)
|
||||
randomHostNodeID = report.MakeHostNodeID(randomHostID)
|
||||
|
||||
client54001 = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1)
|
||||
client54002 = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2)
|
||||
unknownClient1 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected
|
||||
unknownClient2 = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped.
|
||||
unknownClient3 = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped
|
||||
server80 = report.MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache
|
||||
|
||||
clientIP = report.MakeAddressNodeID(clientHostID, "10.10.10.20")
|
||||
serverIP = report.MakeAddressNodeID(serverHostID, "192.168.1.1")
|
||||
randomIP = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology
|
||||
unknownIP = report.MakeAddressNodeID(unknownHostID, "10.10.10.10")
|
||||
)
|
||||
|
||||
var (
|
||||
rpt = report.Report{
|
||||
Endpoint: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(client54001): report.MakeIDList(server80),
|
||||
report.MakeAdjacencyID(client54002): report.MakeIDList(server80),
|
||||
report.MakeAdjacencyID(server80): report.MakeIDList(client54001, client54002, unknownClient1, unknownClient2, unknownClient3),
|
||||
},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
// NodeMetadata is arbitrary. We're free to put only precisely what we
|
||||
// care to test into the fixture. Just be sure to include the bits
|
||||
// that the mapping funcs extract :)
|
||||
client54001: report.NodeMetadata{
|
||||
"name": "curl",
|
||||
"domain": "client-54001-domain",
|
||||
"pid": "10001",
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
client54002: report.NodeMetadata{
|
||||
"name": "curl", // should be same as above!
|
||||
"domain": "client-54002-domain", // may be different than above
|
||||
"pid": "10001", // should be same as above!
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
server80: report.NodeMetadata{
|
||||
"name": "apache",
|
||||
"domain": "server-80-domain",
|
||||
"pid": "215",
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{
|
||||
report.MakeEdgeID(client54001, server80): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 100,
|
||||
BytesEgress: 10,
|
||||
},
|
||||
report.MakeEdgeID(client54002, server80): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 200,
|
||||
BytesEgress: 20,
|
||||
},
|
||||
|
||||
report.MakeEdgeID(server80, client54001): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 10,
|
||||
BytesEgress: 100,
|
||||
},
|
||||
report.MakeEdgeID(server80, client54002): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 20,
|
||||
BytesEgress: 200,
|
||||
},
|
||||
report.MakeEdgeID(server80, unknownClient1): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 30,
|
||||
BytesEgress: 300,
|
||||
},
|
||||
report.MakeEdgeID(server80, unknownClient2): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 40,
|
||||
BytesEgress: 400,
|
||||
},
|
||||
report.MakeEdgeID(server80, unknownClient3): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 50,
|
||||
BytesEgress: 500,
|
||||
},
|
||||
},
|
||||
},
|
||||
Address: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(clientIP): report.MakeIDList(serverIP),
|
||||
report.MakeAdjacencyID(randomIP): report.MakeIDList(serverIP),
|
||||
report.MakeAdjacencyID(serverIP): report.MakeIDList(clientIP, unknownIP), // no backlink to random
|
||||
},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
clientIP: report.NodeMetadata{
|
||||
"name": "client.hostname.com", // hostname
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
randomIP: report.NodeMetadata{
|
||||
"name": "random.hostname.com", // hostname
|
||||
report.HostNodeID: randomHostNodeID,
|
||||
},
|
||||
serverIP: report.NodeMetadata{
|
||||
"name": "server.hostname.com", // hostname
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{
|
||||
report.MakeEdgeID(clientIP, serverIP): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
report.MakeEdgeID(randomIP, serverIP): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 20, // dangling connections, weird but possible
|
||||
},
|
||||
report.MakeEdgeID(serverIP, clientIP): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
report.MakeEdgeID(serverIP, unknownIP): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestRenderByEndpointPID(t *testing.T) {
|
||||
want := report.RenderableNodes{
|
||||
"pid:client-54001-domain:10001": {
|
||||
ID: "pid:client-54001-domain:10001",
|
||||
LabelMajor: "curl",
|
||||
LabelMinor: "client-54001-domain (10001)",
|
||||
Rank: "10001",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList("pid:server-80-domain:215"),
|
||||
Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001")),
|
||||
Metadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 100,
|
||||
report.KeyBytesEgress: 10,
|
||||
},
|
||||
},
|
||||
"pid:client-54002-domain:10001": {
|
||||
ID: "pid:client-54002-domain:10001",
|
||||
LabelMajor: "curl",
|
||||
LabelMinor: "client-54002-domain (10001)",
|
||||
Rank: "10001", // same process
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList("pid:server-80-domain:215"),
|
||||
Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")),
|
||||
Metadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 200,
|
||||
report.KeyBytesEgress: 20,
|
||||
},
|
||||
},
|
||||
"pid:server-80-domain:215": {
|
||||
ID: "pid:server-80-domain:215",
|
||||
LabelMajor: "apache",
|
||||
LabelMinor: "server-80-domain (215)",
|
||||
Rank: "215",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(
|
||||
"pid:client-54001-domain:10001",
|
||||
"pid:client-54002-domain:10001",
|
||||
"pseudo;10.10.10.10;192.168.1.1;80",
|
||||
"pseudo;10.10.10.11;192.168.1.1;80",
|
||||
),
|
||||
Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")),
|
||||
Metadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 150,
|
||||
report.KeyBytesEgress: 1500,
|
||||
},
|
||||
},
|
||||
"pseudo;10.10.10.10;192.168.1.1;80": {
|
||||
ID: "pseudo;10.10.10.10;192.168.1.1;80",
|
||||
LabelMajor: "10.10.10.10",
|
||||
Pseudo: true,
|
||||
Metadata: report.AggregateMetadata{},
|
||||
},
|
||||
"pseudo;10.10.10.11;192.168.1.1;80": {
|
||||
ID: "pseudo;10.10.10.11;192.168.1.1;80",
|
||||
LabelMajor: "10.10.10.11",
|
||||
Pseudo: true,
|
||||
Metadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := renderTopology(rpt.Endpoint, ProcessPID, GenericPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderByEndpointPIDGrouped(t *testing.T) {
|
||||
// For grouped, I've somewhat arbitrarily chosen to squash together all
|
||||
// processes with the same name by removing the PID and domain (host)
|
||||
// dimensions from the ID. That could be changed.
|
||||
want := report.RenderableNodes{
|
||||
"curl": {
|
||||
ID: "curl",
|
||||
LabelMajor: "curl",
|
||||
LabelMinor: "",
|
||||
Rank: "curl",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList("apache"),
|
||||
Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001"), report.MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")),
|
||||
Metadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 300,
|
||||
report.KeyBytesEgress: 30,
|
||||
},
|
||||
},
|
||||
"apache": {
|
||||
ID: "apache",
|
||||
LabelMajor: "apache",
|
||||
LabelMinor: "",
|
||||
Rank: "apache",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(
|
||||
"curl",
|
||||
"pseudo;10.10.10.10;apache",
|
||||
"pseudo;10.10.10.11;apache",
|
||||
),
|
||||
Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")),
|
||||
Metadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 150,
|
||||
report.KeyBytesEgress: 1500,
|
||||
},
|
||||
},
|
||||
"pseudo;10.10.10.10;apache": {
|
||||
ID: "pseudo;10.10.10.10;apache",
|
||||
LabelMajor: "10.10.10.10",
|
||||
Pseudo: true,
|
||||
Metadata: report.AggregateMetadata{},
|
||||
},
|
||||
"pseudo;10.10.10.11;apache": {
|
||||
ID: "pseudo;10.10.10.11;apache",
|
||||
LabelMajor: "10.10.10.11",
|
||||
Pseudo: true,
|
||||
Metadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := renderTopology(rpt.Endpoint, ProcessName, GenericGroupedPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderByNetworkHostname(t *testing.T) {
|
||||
want := report.RenderableNodes{
|
||||
"host:client.hostname.com": {
|
||||
ID: "host:client.hostname.com",
|
||||
LabelMajor: "client", // before first .
|
||||
LabelMinor: "hostname.com", // after first .
|
||||
Rank: "client",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList("host:server.hostname.com"),
|
||||
Origins: report.MakeIDList(report.MakeHostNodeID("client.hostname.com"), report.MakeAddressNodeID("client.hostname.com", "10.10.10.20")),
|
||||
Metadata: report.AggregateMetadata{
|
||||
report.KeyMaxConnCountTCP: 3,
|
||||
},
|
||||
},
|
||||
"host:random.hostname.com": {
|
||||
ID: "host:random.hostname.com",
|
||||
LabelMajor: "random", // before first .
|
||||
LabelMinor: "hostname.com", // after first .
|
||||
Rank: "random",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList("host:server.hostname.com"),
|
||||
Origins: report.MakeIDList(report.MakeHostNodeID("random.hostname.com"), report.MakeAddressNodeID("random.hostname.com", "172.16.11.9")),
|
||||
Metadata: report.AggregateMetadata{
|
||||
report.KeyMaxConnCountTCP: 20,
|
||||
},
|
||||
},
|
||||
"host:server.hostname.com": {
|
||||
ID: "host:server.hostname.com",
|
||||
LabelMajor: "server", // before first .
|
||||
LabelMinor: "hostname.com", // after first .
|
||||
Rank: "server",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList("host:client.hostname.com", "pseudo;10.10.10.10;192.168.1.1;"),
|
||||
Origins: report.MakeIDList(report.MakeHostNodeID("server.hostname.com"), report.MakeAddressNodeID("server.hostname.com", "192.168.1.1")),
|
||||
Metadata: report.AggregateMetadata{
|
||||
report.KeyMaxConnCountTCP: 10,
|
||||
},
|
||||
},
|
||||
"pseudo;10.10.10.10;192.168.1.1;": {
|
||||
ID: "pseudo;10.10.10.10;192.168.1.1;",
|
||||
LabelMajor: "10.10.10.10",
|
||||
LabelMinor: "", // after first .
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
Adjacency: nil,
|
||||
Origins: nil,
|
||||
Metadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := renderTopology(rpt.Address, NetworkHostname, GenericPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func diff(want, have interface{}) string {
|
||||
text, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
||||
A: difflib.SplitLines(spew.Sdump(want)),
|
||||
B: difflib.SplitLines(spew.Sdump(have)),
|
||||
FromFile: "want",
|
||||
ToFile: "have",
|
||||
Context: 3,
|
||||
})
|
||||
return "\n" + text
|
||||
}
|
||||
|
||||
@@ -82,6 +82,24 @@ type Row struct {
|
||||
ValueMinor string `json:"value_minor,omitempty"` // e.g. KB/s
|
||||
}
|
||||
|
||||
// TopologySelector selects a single topology from a report.
|
||||
type TopologySelector func(r Report) Topology
|
||||
|
||||
// SelectEndpoint selects the endpoint topology.
|
||||
func SelectEndpoint(r Report) Topology {
|
||||
return r.Endpoint
|
||||
}
|
||||
|
||||
// SelectAddress selects the address topology.
|
||||
func SelectAddress(r Report) Topology {
|
||||
return r.Address
|
||||
}
|
||||
|
||||
// SelectContainer selects the container topology.
|
||||
func SelectContainer(r Report) Topology {
|
||||
return r.Container
|
||||
}
|
||||
|
||||
// MakeReport makes a clean report, ready to Merge() other reports into.
|
||||
func MakeReport() Report {
|
||||
return Report{
|
||||
|
||||
@@ -2,7 +2,6 @@ package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -77,110 +76,6 @@ func NewTopology() Topology {
|
||||
}
|
||||
}
|
||||
|
||||
// RenderBy transforms a given Topology into a set of RenderableNodes, which
|
||||
// the UI will render collectively as a graph. Note that a RenderableNode will
|
||||
// always be rendered with other nodes, and therefore contains limited detail.
|
||||
//
|
||||
// RenderBy takes a a MapFunc, which defines how to group and label nodes. Npdes
|
||||
// with the same mapped IDs will be merged.
|
||||
func (t Topology) RenderBy(mapFunc MapFunc, pseudoFunc PseudoFunc) RenderableNodes {
|
||||
nodes := RenderableNodes{}
|
||||
|
||||
// Build a set of RenderableNodes for all non-pseudo probes, and an
|
||||
// addressID to nodeID lookup map. Multiple addressIDs can map to the same
|
||||
// RenderableNodes.
|
||||
var (
|
||||
source2mapped = map[string]string{} // source node ID -> mapped node ID
|
||||
source2host = map[string]string{} // source node ID -> origin host ID
|
||||
)
|
||||
for nodeID, metadata := range t.NodeMetadatas {
|
||||
mapped, ok := mapFunc(metadata)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// mapped.ID needs not be unique over all addressIDs. If not, we merge with
|
||||
// the existing data, on the assumption that the MapFunc returns the same
|
||||
// data.
|
||||
existing, ok := nodes[mapped.ID]
|
||||
if ok {
|
||||
mapped.Merge(existing)
|
||||
}
|
||||
|
||||
mapped.Origins = mapped.Origins.Add(nodeID)
|
||||
nodes[mapped.ID] = mapped
|
||||
source2mapped[nodeID] = mapped.ID
|
||||
source2host[nodeID] = metadata[HostNodeID]
|
||||
}
|
||||
|
||||
// Walk the graph and make connections.
|
||||
for src, dsts := range t.Adjacency {
|
||||
var (
|
||||
srcNodeID, ok = ParseAdjacencyID(src)
|
||||
//srcOriginHostID, _, ok2 = ParseNodeID(srcNodeID)
|
||||
srcHostNodeID = source2host[srcNodeID]
|
||||
srcRenderableID = source2mapped[srcNodeID] // must exist
|
||||
srcRenderableNode = nodes[srcRenderableID] // must exist
|
||||
)
|
||||
if !ok {
|
||||
log.Printf("bad adjacency ID %q", src)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, dstNodeID := range dsts {
|
||||
dstRenderableID, ok := source2mapped[dstNodeID]
|
||||
if !ok {
|
||||
pseudoNode, ok := pseudoFunc(srcNodeID, srcRenderableNode, dstNodeID)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
dstRenderableID = pseudoNode.ID
|
||||
nodes[dstRenderableID] = pseudoNode
|
||||
source2mapped[dstNodeID] = dstRenderableID
|
||||
}
|
||||
|
||||
srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID)
|
||||
srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcHostNodeID)
|
||||
srcRenderableNode.Origins = srcRenderableNode.Origins.Add(srcNodeID)
|
||||
edgeID := MakeEdgeID(srcNodeID, dstNodeID)
|
||||
if md, ok := t.EdgeMetadatas[edgeID]; ok {
|
||||
srcRenderableNode.Metadata.Merge(md.Transform())
|
||||
}
|
||||
}
|
||||
|
||||
nodes[srcRenderableID] = srcRenderableNode
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
// EdgeMetadata gives the metadata of an edge from the perspective of the
|
||||
// srcRenderableID. Since an edgeID can have multiple edges on the address
|
||||
// level, it uses the supplied mapping function to translate address IDs to
|
||||
// renderable node (mapped) IDs.
|
||||
func (t Topology) EdgeMetadata(mapFunc MapFunc, srcRenderableID, dstRenderableID string) EdgeMetadata {
|
||||
metadata := EdgeMetadata{}
|
||||
for edgeID, edgeMeta := range t.EdgeMetadatas {
|
||||
src, dst, ok := ParseEdgeID(edgeID)
|
||||
if !ok {
|
||||
log.Printf("bad edge ID %q", edgeID)
|
||||
continue
|
||||
}
|
||||
if src != TheInternet {
|
||||
mapped, _ := mapFunc(t.NodeMetadatas[src])
|
||||
src = mapped.ID
|
||||
}
|
||||
if dst != TheInternet {
|
||||
mapped, _ := mapFunc(t.NodeMetadatas[dst])
|
||||
dst = mapped.ID
|
||||
}
|
||||
if src == srcRenderableID && dst == dstRenderableID {
|
||||
metadata.Flatten(edgeMeta)
|
||||
}
|
||||
}
|
||||
return metadata
|
||||
}
|
||||
|
||||
// Squash squashes all non-local nodes in the topology to a super-node called
|
||||
// the Internet.
|
||||
// We rely on the values in the t.Adjacency lists being valid keys in
|
||||
|
||||
@@ -4,324 +4,8 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
)
|
||||
|
||||
func init() {
|
||||
spew.Config.SortKeys = true // :\
|
||||
}
|
||||
|
||||
var (
|
||||
clientHostID = "client.hostname.com"
|
||||
serverHostID = "server.hostname.com"
|
||||
randomHostID = "random.hostname.com"
|
||||
unknownHostID = ""
|
||||
|
||||
clientHostNodeID = MakeHostNodeID(clientHostID)
|
||||
serverHostNodeID = MakeHostNodeID(serverHostID)
|
||||
randomHostNodeID = MakeHostNodeID(randomHostID)
|
||||
|
||||
client54001 = MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1)
|
||||
client54002 = MakeEndpointNodeID(clientHostID, "10.10.10.20", "54002") // curl (2)
|
||||
unknownClient1 = MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected
|
||||
unknownClient2 = MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped.
|
||||
unknownClient3 = MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped
|
||||
server80 = MakeEndpointNodeID(serverHostID, "192.168.1.1", "80") // apache
|
||||
|
||||
clientIP = MakeAddressNodeID(clientHostID, "10.10.10.20")
|
||||
serverIP = MakeAddressNodeID(serverHostID, "192.168.1.1")
|
||||
randomIP = MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology
|
||||
unknownIP = MakeAddressNodeID(unknownHostID, "10.10.10.10")
|
||||
)
|
||||
|
||||
var (
|
||||
report = Report{
|
||||
Endpoint: Topology{
|
||||
Adjacency: Adjacency{
|
||||
MakeAdjacencyID(client54001): MakeIDList(server80),
|
||||
MakeAdjacencyID(client54002): MakeIDList(server80),
|
||||
MakeAdjacencyID(server80): MakeIDList(client54001, client54002, unknownClient1, unknownClient2, unknownClient3),
|
||||
},
|
||||
NodeMetadatas: NodeMetadatas{
|
||||
// NodeMetadata is arbitrary. We're free to put only precisely what we
|
||||
// care to test into the fixture. Just be sure to include the bits
|
||||
// that the mapping funcs extract :)
|
||||
client54001: NodeMetadata{
|
||||
"name": "curl",
|
||||
"domain": "client-54001-domain",
|
||||
"pid": "10001",
|
||||
HostNodeID: clientHostNodeID,
|
||||
},
|
||||
client54002: NodeMetadata{
|
||||
"name": "curl", // should be same as above!
|
||||
"domain": "client-54002-domain", // may be different than above
|
||||
"pid": "10001", // should be same as above!
|
||||
HostNodeID: clientHostNodeID,
|
||||
},
|
||||
server80: NodeMetadata{
|
||||
"name": "apache",
|
||||
"domain": "server-80-domain",
|
||||
"pid": "215",
|
||||
HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: EdgeMetadatas{
|
||||
MakeEdgeID(client54001, server80): EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 100,
|
||||
BytesEgress: 10,
|
||||
},
|
||||
MakeEdgeID(client54002, server80): EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 200,
|
||||
BytesEgress: 20,
|
||||
},
|
||||
|
||||
MakeEdgeID(server80, client54001): EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 10,
|
||||
BytesEgress: 100,
|
||||
},
|
||||
MakeEdgeID(server80, client54002): EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 20,
|
||||
BytesEgress: 200,
|
||||
},
|
||||
MakeEdgeID(server80, unknownClient1): EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 30,
|
||||
BytesEgress: 300,
|
||||
},
|
||||
MakeEdgeID(server80, unknownClient2): EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 40,
|
||||
BytesEgress: 400,
|
||||
},
|
||||
MakeEdgeID(server80, unknownClient3): EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 50,
|
||||
BytesEgress: 500,
|
||||
},
|
||||
},
|
||||
},
|
||||
Address: Topology{
|
||||
Adjacency: Adjacency{
|
||||
MakeAdjacencyID(clientIP): MakeIDList(serverIP),
|
||||
MakeAdjacencyID(randomIP): MakeIDList(serverIP),
|
||||
MakeAdjacencyID(serverIP): MakeIDList(clientIP, unknownIP), // no backlink to random
|
||||
},
|
||||
NodeMetadatas: NodeMetadatas{
|
||||
clientIP: NodeMetadata{
|
||||
"name": "client.hostname.com", // hostname
|
||||
HostNodeID: clientHostNodeID,
|
||||
},
|
||||
randomIP: NodeMetadata{
|
||||
"name": "random.hostname.com", // hostname
|
||||
HostNodeID: randomHostNodeID,
|
||||
},
|
||||
serverIP: NodeMetadata{
|
||||
"name": "server.hostname.com", // hostname
|
||||
HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: EdgeMetadatas{
|
||||
MakeEdgeID(clientIP, serverIP): EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
MakeEdgeID(randomIP, serverIP): EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 20, // dangling connections, weird but possible
|
||||
},
|
||||
MakeEdgeID(serverIP, clientIP): EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
MakeEdgeID(serverIP, unknownIP): EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestRenderByEndpointPID(t *testing.T) {
|
||||
want := RenderableNodes{
|
||||
"pid:client-54001-domain:10001": {
|
||||
ID: "pid:client-54001-domain:10001",
|
||||
LabelMajor: "curl",
|
||||
LabelMinor: "client-54001-domain (10001)",
|
||||
Rank: "10001",
|
||||
Pseudo: false,
|
||||
Adjacency: MakeIDList("pid:server-80-domain:215"),
|
||||
Origins: MakeIDList(MakeHostNodeID("client.hostname.com"), MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001")),
|
||||
Metadata: AggregateMetadata{
|
||||
KeyBytesIngress: 100,
|
||||
KeyBytesEgress: 10,
|
||||
},
|
||||
},
|
||||
"pid:client-54002-domain:10001": {
|
||||
ID: "pid:client-54002-domain:10001",
|
||||
LabelMajor: "curl",
|
||||
LabelMinor: "client-54002-domain (10001)",
|
||||
Rank: "10001", // same process
|
||||
Pseudo: false,
|
||||
Adjacency: MakeIDList("pid:server-80-domain:215"),
|
||||
Origins: MakeIDList(MakeHostNodeID("client.hostname.com"), MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")),
|
||||
Metadata: AggregateMetadata{
|
||||
KeyBytesIngress: 200,
|
||||
KeyBytesEgress: 20,
|
||||
},
|
||||
},
|
||||
"pid:server-80-domain:215": {
|
||||
ID: "pid:server-80-domain:215",
|
||||
LabelMajor: "apache",
|
||||
LabelMinor: "server-80-domain (215)",
|
||||
Rank: "215",
|
||||
Pseudo: false,
|
||||
Adjacency: MakeIDList(
|
||||
"pid:client-54001-domain:10001",
|
||||
"pid:client-54002-domain:10001",
|
||||
"pseudo;10.10.10.10;192.168.1.1;80",
|
||||
"pseudo;10.10.10.11;192.168.1.1;80",
|
||||
),
|
||||
Origins: MakeIDList(MakeHostNodeID("server.hostname.com"), MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")),
|
||||
Metadata: AggregateMetadata{
|
||||
KeyBytesIngress: 150,
|
||||
KeyBytesEgress: 1500,
|
||||
},
|
||||
},
|
||||
"pseudo;10.10.10.10;192.168.1.1;80": {
|
||||
ID: "pseudo;10.10.10.10;192.168.1.1;80",
|
||||
LabelMajor: "10.10.10.10",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
"pseudo;10.10.10.11;192.168.1.1;80": {
|
||||
ID: "pseudo;10.10.10.11;192.168.1.1;80",
|
||||
LabelMajor: "10.10.10.11",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := report.Endpoint.RenderBy(ProcessPID, GenericPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderByEndpointPIDGrouped(t *testing.T) {
|
||||
// For grouped, I've somewhat arbitrarily chosen to squash together all
|
||||
// processes with the same name by removing the PID and domain (host)
|
||||
// dimensions from the ID. That could be changed.
|
||||
want := RenderableNodes{
|
||||
"curl": {
|
||||
ID: "curl",
|
||||
LabelMajor: "curl",
|
||||
LabelMinor: "",
|
||||
Rank: "curl",
|
||||
Pseudo: false,
|
||||
Adjacency: MakeIDList("apache"),
|
||||
Origins: MakeIDList(MakeHostNodeID("client.hostname.com"), MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54001"), MakeEndpointNodeID("client.hostname.com", "10.10.10.20", "54002")),
|
||||
Metadata: AggregateMetadata{
|
||||
KeyBytesIngress: 300,
|
||||
KeyBytesEgress: 30,
|
||||
},
|
||||
},
|
||||
"apache": {
|
||||
ID: "apache",
|
||||
LabelMajor: "apache",
|
||||
LabelMinor: "",
|
||||
Rank: "apache",
|
||||
Pseudo: false,
|
||||
Adjacency: MakeIDList(
|
||||
"curl",
|
||||
"pseudo;10.10.10.10;apache",
|
||||
"pseudo;10.10.10.11;apache",
|
||||
),
|
||||
Origins: MakeIDList(MakeHostNodeID("server.hostname.com"), MakeEndpointNodeID("server.hostname.com", "192.168.1.1", "80")),
|
||||
Metadata: AggregateMetadata{
|
||||
KeyBytesIngress: 150,
|
||||
KeyBytesEgress: 1500,
|
||||
},
|
||||
},
|
||||
"pseudo;10.10.10.10;apache": {
|
||||
ID: "pseudo;10.10.10.10;apache",
|
||||
LabelMajor: "10.10.10.10",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
"pseudo;10.10.10.11;apache": {
|
||||
ID: "pseudo;10.10.10.11;apache",
|
||||
LabelMajor: "10.10.10.11",
|
||||
Pseudo: true,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := report.Endpoint.RenderBy(ProcessName, GenericGroupedPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderByNetworkHostname(t *testing.T) {
|
||||
want := RenderableNodes{
|
||||
"host:client.hostname.com": {
|
||||
ID: "host:client.hostname.com",
|
||||
LabelMajor: "client", // before first .
|
||||
LabelMinor: "hostname.com", // after first .
|
||||
Rank: "client",
|
||||
Pseudo: false,
|
||||
Adjacency: MakeIDList("host:server.hostname.com"),
|
||||
Origins: MakeIDList(MakeHostNodeID("client.hostname.com"), MakeAddressNodeID("client.hostname.com", "10.10.10.20")),
|
||||
Metadata: AggregateMetadata{
|
||||
KeyMaxConnCountTCP: 3,
|
||||
},
|
||||
},
|
||||
"host:random.hostname.com": {
|
||||
ID: "host:random.hostname.com",
|
||||
LabelMajor: "random", // before first .
|
||||
LabelMinor: "hostname.com", // after first .
|
||||
Rank: "random",
|
||||
Pseudo: false,
|
||||
Adjacency: MakeIDList("host:server.hostname.com"),
|
||||
Origins: MakeIDList(MakeHostNodeID("random.hostname.com"), MakeAddressNodeID("random.hostname.com", "172.16.11.9")),
|
||||
Metadata: AggregateMetadata{
|
||||
KeyMaxConnCountTCP: 20,
|
||||
},
|
||||
},
|
||||
"host:server.hostname.com": {
|
||||
ID: "host:server.hostname.com",
|
||||
LabelMajor: "server", // before first .
|
||||
LabelMinor: "hostname.com", // after first .
|
||||
Rank: "server",
|
||||
Pseudo: false,
|
||||
Adjacency: MakeIDList("host:client.hostname.com", "pseudo;10.10.10.10;192.168.1.1;"),
|
||||
Origins: MakeIDList(MakeHostNodeID("server.hostname.com"), MakeAddressNodeID("server.hostname.com", "192.168.1.1")),
|
||||
Metadata: AggregateMetadata{
|
||||
KeyMaxConnCountTCP: 10,
|
||||
},
|
||||
},
|
||||
"pseudo;10.10.10.10;192.168.1.1;": {
|
||||
ID: "pseudo;10.10.10.10;192.168.1.1;",
|
||||
LabelMajor: "10.10.10.10",
|
||||
LabelMinor: "", // after first .
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
Adjacency: nil,
|
||||
Origins: nil,
|
||||
Metadata: AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := report.Address.RenderBy(NetworkHostname, GenericPseudoNode)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopoDiff(t *testing.T) {
|
||||
nodea := RenderableNode{
|
||||
ID: "nodea",
|
||||
@@ -394,18 +78,7 @@ func TestTopoDiff(t *testing.T) {
|
||||
sort.Sort(ByID(c.have.Add))
|
||||
sort.Sort(ByID(c.have.Update))
|
||||
if !reflect.DeepEqual(c.want, c.have) {
|
||||
t.Errorf("%s\n%s", c.label, diff(c.want, c.have))
|
||||
t.Errorf("%s - want:%s have:%s", c.label, c.want, c.have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func diff(want, have interface{}) string {
|
||||
text, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
||||
A: difflib.SplitLines(spew.Sdump(want)),
|
||||
B: difflib.SplitLines(spew.Sdump(have)),
|
||||
FromFile: "want",
|
||||
ToFile: "have",
|
||||
Context: 3,
|
||||
})
|
||||
return "\n" + text
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user