mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-04 02:30:45 +00:00
Merge pull request #248 from tomwilkie/228-dev
Produce containers topology from endpoints via processes
This commit is contained in:
@@ -26,15 +26,15 @@ func TestAPITopology(t *testing.T) {
|
||||
}
|
||||
|
||||
if have := topology.Stats.EdgeCount; have <= 0 {
|
||||
t.Errorf("EdgeCount isn't positive: %d", have)
|
||||
t.Errorf("EdgeCount isn't positive for %s: %d", topology.Name, have)
|
||||
}
|
||||
|
||||
if have := topology.Stats.NodeCount; have <= 0 {
|
||||
t.Errorf("NodeCount isn't positive: %d", have)
|
||||
t.Errorf("NodeCount isn't positive for %s: %d", topology.Name, have)
|
||||
}
|
||||
|
||||
if have := topology.Stats.NonpseudoNodeCount; have <= 0 {
|
||||
t.Errorf("NonpseudoNodeCount isn't positive: %d", have)
|
||||
t.Errorf("NonpseudoNodeCount isn't positive for %s: %d", topology.Name, have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,36 +23,37 @@ func TestAPITopologyApplications(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
equals(t, 4, len(topo.Nodes))
|
||||
node, ok := topo.Nodes["pid:node-a.local:23128"]
|
||||
node, ok := topo.Nodes["pid:hostA:23128"]
|
||||
if !ok {
|
||||
t.Errorf("missing curl node")
|
||||
}
|
||||
equals(t, 1, len(node.Adjacency))
|
||||
equals(t, report.MakeIDList("pid:node-b.local:215"), node.Adjacency)
|
||||
equals(t, report.MakeIDList("pid:hostB:215"), node.Adjacency)
|
||||
equals(t, report.MakeIDList(
|
||||
report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345"),
|
||||
report.MakeEndpointNodeID("hostA", "192.168.1.1", "12346"),
|
||||
report.MakeProcessNodeID("hostA", "23128"),
|
||||
report.MakeHostNodeID("hostA"),
|
||||
), node.Origins)
|
||||
equals(t, "curl", node.LabelMajor)
|
||||
equals(t, "node-a.local (23128)", node.LabelMinor)
|
||||
equals(t, "hostA (23128)", node.LabelMinor)
|
||||
equals(t, "23128", node.Rank)
|
||||
equals(t, false, node.Pseudo)
|
||||
}
|
||||
{
|
||||
body := getRawJSON(t, ts, "/api/topology/applications/pid:node-a.local:23128")
|
||||
body := getRawJSON(t, ts, "/api/topology/applications/pid:hostA:23128")
|
||||
var node APINode
|
||||
if err := json.Unmarshal(body, &node); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
equals(t, "pid:node-a.local:23128", node.Node.ID)
|
||||
equals(t, "pid:hostA:23128", node.Node.ID)
|
||||
equals(t, "curl", node.Node.LabelMajor)
|
||||
equals(t, "node-a.local (23128)", node.Node.LabelMinor)
|
||||
equals(t, "hostA (23128)", node.Node.LabelMinor)
|
||||
equals(t, false, node.Node.Pseudo)
|
||||
// Let's not unit-test the specific content of the detail tables
|
||||
}
|
||||
{
|
||||
body := getRawJSON(t, ts, "/api/topology/applications/pid:node-a.local:23128/pid:node-b.local:215")
|
||||
body := getRawJSON(t, ts, "/api/topology/applications/pid:hostA:23128/pid:hostB:215")
|
||||
var edge APIEdge
|
||||
if err := json.Unmarshal(body, &edge); err != nil {
|
||||
t.Fatalf("JSON parse error: %s", err)
|
||||
|
||||
@@ -55,32 +55,63 @@ func (s StaticReport) Report() report.Report {
|
||||
},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345"): report.NodeMetadata{
|
||||
"addr": "192.168.1.1",
|
||||
"port": "12345",
|
||||
"pid": "23128",
|
||||
"name": "curl",
|
||||
"domain": "node-a.local",
|
||||
report.HostNodeID: report.MakeHostNodeID("hostA"),
|
||||
},
|
||||
report.MakeEndpointNodeID("hostA", "192.168.1.1", "12346"): report.NodeMetadata{ // <-- same as :12345
|
||||
"addr": "192.168.1.1",
|
||||
"port": "12346",
|
||||
"pid": "23128",
|
||||
"name": "curl",
|
||||
"domain": "node-a.local",
|
||||
report.HostNodeID: report.MakeHostNodeID("hostA"),
|
||||
},
|
||||
report.MakeEndpointNodeID("hostA", "192.168.1.1", "8888"): report.NodeMetadata{
|
||||
"addr": "192.168.1.1",
|
||||
"port": "8888",
|
||||
"pid": "55100",
|
||||
"name": "ssh",
|
||||
"domain": "node-a.local",
|
||||
report.HostNodeID: report.MakeHostNodeID("hostA"),
|
||||
},
|
||||
report.MakeEndpointNodeID("hostB", "192.168.1.2", "80"): report.NodeMetadata{
|
||||
"addr": "192.168.1.2",
|
||||
"port": "80",
|
||||
"pid": "215",
|
||||
"name": "apache",
|
||||
"domain": "node-b.local",
|
||||
report.HostNodeID: report.MakeHostNodeID("hostB"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Process: report.Topology{
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
report.MakeProcessNodeID("hostA", "23128"): report.NodeMetadata{
|
||||
"pid": "23128",
|
||||
"comm": "curl",
|
||||
report.HostNodeID: report.MakeHostNodeID("hostA"),
|
||||
},
|
||||
report.MakeProcessNodeID("hostA", "8888"): report.NodeMetadata{
|
||||
"pid": "8888",
|
||||
"comm": "ssh",
|
||||
report.HostNodeID: report.MakeHostNodeID("hostA"),
|
||||
},
|
||||
report.MakeProcessNodeID("hostB", "80"): report.NodeMetadata{
|
||||
"pid": "80",
|
||||
"comm": "apache",
|
||||
"docker_container_id": "abcdefg",
|
||||
report.HostNodeID: report.MakeHostNodeID("hostB"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Container: report.Topology{
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
report.MakeContainerNodeID("hostB", "abcdefg"): report.NodeMetadata{
|
||||
"docker_container_id": "abcdefg",
|
||||
"docker_container_name": "server",
|
||||
report.HostNodeID: report.MakeHostNodeID("hostB"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Address: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(report.MakeAddressNodeID("hostA", "192.168.1.1")): report.MakeIDList(report.MakeAddressNodeID("hostB", "192.168.1.2"), report.MakeAddressNodeID("", "1.2.3.4")),
|
||||
|
||||
@@ -50,30 +50,27 @@ var topologyRegistry = map[string]topologyView{
|
||||
"applications": {
|
||||
human: "Applications",
|
||||
parent: "",
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessPID, Pseudo: render.GenericPseudoNode},
|
||||
renderer: render.FilterUnconnected{Renderer: render.ProcessRenderer},
|
||||
},
|
||||
"applications-by-name": {
|
||||
human: "by name",
|
||||
parent: "applications",
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessName, Pseudo: render.GenericGroupedPseudoNode},
|
||||
renderer: render.FilterUnconnected{Renderer: render.ProcessNameRenderer},
|
||||
},
|
||||
"containers": {
|
||||
human: "Containers",
|
||||
parent: "",
|
||||
renderer: render.Reduce([]render.Renderer{
|
||||
render.Map{Selector: report.SelectEndpoint, Mapper: render.MapEndpoint2Container, Pseudo: render.InternetOnlyPseudoNode},
|
||||
render.Map{Selector: report.SelectContainer, Mapper: render.MapContainerIdentity, Pseudo: render.InternetOnlyPseudoNode},
|
||||
}),
|
||||
human: "Containers",
|
||||
parent: "",
|
||||
renderer: render.ContainerRenderer,
|
||||
},
|
||||
"containers-by-image": {
|
||||
human: "by image",
|
||||
parent: "containers",
|
||||
renderer: render.Map{Selector: report.SelectEndpoint, Mapper: render.ProcessContainerImage, Pseudo: render.InternetOnlyPseudoNode},
|
||||
renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessContainerImage, Pseudo: render.InternetOnlyPseudoNode},
|
||||
},
|
||||
"hosts": {
|
||||
human: "Hosts",
|
||||
parent: "",
|
||||
renderer: render.Map{Selector: report.SelectAddress, Mapper: render.NetworkHostname, Pseudo: render.GenericPseudoNode},
|
||||
renderer: render.LeafMap{Selector: report.SelectAddress, Mapper: render.NetworkHostname, Pseudo: render.GenericPseudoNode},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ func handleTXT(r Reporter) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
|
||||
renderer := render.Map{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil}
|
||||
renderer := render.LeafMap{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil}
|
||||
dot(w, renderer.Render(r.Report()))
|
||||
|
||||
//report.Render(r.Report(), report.SelectEndpoint, mapFunc(req), report.NoPseudoNode))
|
||||
@@ -35,7 +35,7 @@ func handleSVG(r Reporter) http.HandlerFunc {
|
||||
|
||||
cmd.Stdout = w
|
||||
|
||||
renderer := render.Map{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil}
|
||||
renderer := render.LeafMap{Selector: report.SelectEndpoint, Mapper: mapFunc(req), Pseudo: nil}
|
||||
dot(wc, renderer.Render(r.Report()))
|
||||
wc.Close()
|
||||
|
||||
@@ -102,12 +102,12 @@ func engine(r *http.Request) string {
|
||||
return engine
|
||||
}
|
||||
|
||||
func mapFunc(r *http.Request) render.MapFunc {
|
||||
func mapFunc(r *http.Request) render.LeafMapFunc {
|
||||
switch strings.ToLower(r.FormValue("map_func")) {
|
||||
case "hosts", "networkhost", "networkhostname":
|
||||
return render.NetworkHostname
|
||||
}
|
||||
return render.ProcessPID
|
||||
return render.MapProcessIdentity
|
||||
}
|
||||
|
||||
func classView(r *http.Request) bool {
|
||||
|
||||
@@ -76,9 +76,9 @@ func addConnection(
|
||||
if _, ok := r.Endpoint.NodeMetadatas[scopedLocal]; !ok {
|
||||
// First hit establishes NodeMetadata for scoped local address + port
|
||||
md := report.NodeMetadata{
|
||||
"pid": fmt.Sprintf("%d", c.Proc.PID),
|
||||
"name": c.Proc.Name,
|
||||
"domain": hostID,
|
||||
"addr": c.LocalAddress.String(),
|
||||
"port": strconv.Itoa(int(c.LocalPort)),
|
||||
"pid": fmt.Sprintf("%d", c.Proc.PID),
|
||||
}
|
||||
|
||||
r.Endpoint.NodeMetadatas[scopedLocal] = md
|
||||
|
||||
@@ -124,9 +124,7 @@ func TestSpyWithProcesses(t *testing.T) {
|
||||
}
|
||||
|
||||
for key, want := range map[string]string{
|
||||
"domain": nodeID,
|
||||
"name": fixProcessName,
|
||||
"pid": strconv.FormatUint(uint64(fixProcessPID), 10),
|
||||
"pid": strconv.FormatUint(uint64(fixProcessPID), 10),
|
||||
} {
|
||||
if have := r.Endpoint.NodeMetadatas[scopedLocal][key]; want != have {
|
||||
t.Errorf("Process.NodeMetadatas[%q][%q]: want %q, have %q", scopedLocal, key, want, have)
|
||||
|
||||
@@ -271,7 +271,13 @@ func (t *DockerTagger) Containers() []*docker.Container {
|
||||
|
||||
// Tag implements Tagger.
|
||||
func (t *DockerTagger) Tag(r report.Report) report.Report {
|
||||
for nodeID, nodeMetadata := range r.Endpoint.NodeMetadatas {
|
||||
t.tag(&r.Process)
|
||||
t.tag(&r.Endpoint)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *DockerTagger) tag(topology *report.Topology) {
|
||||
for nodeID, nodeMetadata := range topology.NodeMetadatas {
|
||||
pidStr, ok := nodeMetadata["pid"]
|
||||
if !ok {
|
||||
//log.Printf("dockerTagger: %q: no process node ID", id)
|
||||
@@ -318,10 +324,8 @@ func (t *DockerTagger) Tag(r report.Report) report.Report {
|
||||
md[ImageName] = image.RepoTags[0]
|
||||
}
|
||||
|
||||
r.Endpoint.NodeMetadatas[nodeID].Merge(md)
|
||||
topology.NodeMetadatas[nodeID].Merge(md)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ContainerTopology produces a Toplogy of Containers
|
||||
|
||||
@@ -67,8 +67,8 @@ func TestDockerTagger(t *testing.T) {
|
||||
}
|
||||
|
||||
var (
|
||||
endpoint1NodeID = "somehost.com;192.168.1.1;12345"
|
||||
endpoint2NodeID = "somehost.com;192.168.1.1;67890"
|
||||
pid1NodeID = report.MakeProcessNodeID("somehost.com", "1")
|
||||
pid2NodeID = report.MakeProcessNodeID("somehost.com", "2")
|
||||
endpointNodeMetadata = report.NodeMetadata{
|
||||
ContainerID: "foo",
|
||||
ImageID: "baz",
|
||||
@@ -83,17 +83,17 @@ func TestDockerTagger(t *testing.T) {
|
||||
)
|
||||
|
||||
r := report.MakeReport()
|
||||
r.Endpoint.NodeMetadatas[endpoint1NodeID] = report.NodeMetadata{"pid": "1"}
|
||||
r.Endpoint.NodeMetadatas[endpoint2NodeID] = report.NodeMetadata{"pid": "2"}
|
||||
r.Process.NodeMetadatas[pid1NodeID] = report.NodeMetadata{"pid": "1"}
|
||||
r.Process.NodeMetadatas[pid2NodeID] = report.NodeMetadata{"pid": "2"}
|
||||
|
||||
dockerTagger, _ := NewDockerTagger("/irrelevant", 10*time.Second)
|
||||
runtime.Gosched()
|
||||
for _, endpointNodeID := range []string{endpoint1NodeID, endpoint2NodeID} {
|
||||
for _, nodeID := range []string{pid1NodeID, pid2NodeID} {
|
||||
want := endpointNodeMetadata.Copy()
|
||||
have := dockerTagger.Tag(r).Endpoint.NodeMetadatas[endpointNodeID].Copy()
|
||||
have := dockerTagger.Tag(r).Process.NodeMetadatas[nodeID].Copy()
|
||||
delete(have, "pid")
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("%q: want %+v, have %+v", endpointNodeID, want, have)
|
||||
t.Errorf("%q: want %+v, have %+v", nodeID, want, have)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,13 +38,13 @@ func MakeDetailedNode(r report.Report, n RenderableNode) DetailedNode {
|
||||
tables := []Table{}
|
||||
{
|
||||
rows := []Row{}
|
||||
if val, ok := n.Metadata[report.KeyMaxConnCountTCP]; ok {
|
||||
if val, ok := n.AggregateMetadata[report.KeyMaxConnCountTCP]; ok {
|
||||
rows = append(rows, Row{"TCP connections", strconv.FormatInt(int64(val), 10), ""})
|
||||
}
|
||||
if val, ok := n.Metadata[report.KeyBytesIngress]; ok {
|
||||
if val, ok := n.AggregateMetadata[report.KeyBytesIngress]; ok {
|
||||
rows = append(rows, Row{"Bytes ingress", strconv.FormatInt(int64(val), 10), ""})
|
||||
}
|
||||
if val, ok := n.Metadata[report.KeyBytesEgress]; ok {
|
||||
if val, ok := n.AggregateMetadata[report.KeyBytesEgress]; ok {
|
||||
rows = append(rows, Row{"Bytes egress", strconv.FormatInt(int64(val), 10), ""})
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
@@ -102,10 +102,8 @@ func OriginTable(r report.Report, originID string) (Table, bool) {
|
||||
func endpointOriginTable(nmd report.NodeMetadata) (Table, bool) {
|
||||
rows := []Row{}
|
||||
for _, tuple := range []struct{ key, human string }{
|
||||
{"endpoint", "Endpoint"},
|
||||
{"host_name", "Host name"},
|
||||
{"pid", "PID"},
|
||||
{"name", "Process name"},
|
||||
{"addr", "Endpoint"},
|
||||
{"port", "Port"},
|
||||
} {
|
||||
if val, ok := nmd[tuple.key]; ok {
|
||||
rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""})
|
||||
|
||||
@@ -5,13 +5,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
func TestMakeDetailedNode(t *testing.T) {
|
||||
t.Skip("TODO")
|
||||
}
|
||||
|
||||
func TestOriginTable(t *testing.T) {
|
||||
if _, ok := render.OriginTable(rpt, "not-found"); ok {
|
||||
t.Errorf("unknown origin ID gave unexpected success")
|
||||
@@ -21,9 +16,8 @@ func TestOriginTable(t *testing.T) {
|
||||
Title: "Origin Endpoint",
|
||||
Numeric: false,
|
||||
Rows: []render.Row{
|
||||
{"Host name", clientHostName, ""},
|
||||
{"PID", "10001", ""},
|
||||
{"Process name", "curl", ""},
|
||||
{"Endpoint", clientIP, ""},
|
||||
{"Port", clientPort54001, ""},
|
||||
},
|
||||
},
|
||||
clientAddressNodeID: {
|
||||
@@ -33,12 +27,12 @@ func TestOriginTable(t *testing.T) {
|
||||
{"Host name", clientHostName, ""},
|
||||
},
|
||||
},
|
||||
report.MakeProcessNodeID(clientHostID, "4242"): {
|
||||
serverProcessNodeID: {
|
||||
Title: "Origin Process",
|
||||
Numeric: false,
|
||||
Rows: []render.Row{
|
||||
{"Name (comm)", "curl", ""},
|
||||
{"PID", "4242", ""},
|
||||
{"Name (comm)", "apache", ""},
|
||||
{"PID", serverPID, ""},
|
||||
},
|
||||
},
|
||||
serverHostNodeID: {
|
||||
@@ -61,3 +55,60 @@ func TestOriginTable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeDetailedNode(t *testing.T) {
|
||||
renderableNode := render.ContainerRenderer.Render(rpt)[serverContainerID]
|
||||
have := render.MakeDetailedNode(rpt, renderableNode)
|
||||
want := render.DetailedNode{
|
||||
ID: serverContainerID,
|
||||
LabelMajor: "server",
|
||||
LabelMinor: serverHostName,
|
||||
Pseudo: false,
|
||||
Tables: []render.Table{
|
||||
{
|
||||
Title: "Connections",
|
||||
Numeric: true,
|
||||
Rows: []render.Row{
|
||||
{"Bytes ingress", "150", ""},
|
||||
{"Bytes egress", "1500", ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Origin Endpoint",
|
||||
Numeric: false,
|
||||
Rows: []render.Row{
|
||||
{"Endpoint", "192.168.1.1", ""},
|
||||
{"Port", "80", ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Origin Process",
|
||||
Numeric: false,
|
||||
Rows: []render.Row{
|
||||
{"Name (comm)", "apache", ""},
|
||||
{"PID", "215", ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Origin Container",
|
||||
Numeric: false,
|
||||
Rows: []render.Row{
|
||||
{"Container ID", "5e4d3c2b1a", ""},
|
||||
{"Container name", "server", ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Origin Host",
|
||||
Numeric: false,
|
||||
Rows: []render.Row{
|
||||
{"Host name", "server.hostname.com", ""},
|
||||
{"Load", "0.01 0.01 0.01", ""},
|
||||
{"Operating system", "Linux", ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("%s", diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,31 +7,15 @@ import (
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
const humanTheInternet = "the Internet"
|
||||
// Constants are used in the tests.
|
||||
const (
|
||||
UncontainedID = "uncontained"
|
||||
UncontainedMajor = "Uncontained"
|
||||
|
||||
func newRenderableNode(id, major, minor, rank string) RenderableNode {
|
||||
return RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: minor,
|
||||
Rank: rank,
|
||||
Pseudo: false,
|
||||
Metadata: report.AggregateMetadata{},
|
||||
}
|
||||
}
|
||||
humanTheInternet = "the Internet"
|
||||
)
|
||||
|
||||
func newPseudoNode(id, major, minor string) RenderableNode {
|
||||
return RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: minor,
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
Metadata: report.AggregateMetadata{},
|
||||
}
|
||||
}
|
||||
|
||||
// MapFunc is anything which can take an arbitrary NodeMetadata, which is
|
||||
// LeafMapFunc is anything which can take an arbitrary NodeMetadata, which is
|
||||
// always one-to-one with nodes in a topology, and return a specific
|
||||
// representation of the referenced node, in the form of a node ID and a
|
||||
// human-readable major and minor labels.
|
||||
@@ -41,7 +25,7 @@ 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(report.NodeMetadata) (RenderableNode, bool)
|
||||
type LeafMapFunc func(report.NodeMetadata) (RenderableNode, bool)
|
||||
|
||||
// PseudoFunc creates RenderableNode representing pseudo nodes given the dstNodeID.
|
||||
// The srcNode renderable node is essentially from MapFunc, representing one of
|
||||
@@ -49,52 +33,127 @@ type MapFunc func(report.NodeMetadata) (RenderableNode, bool)
|
||||
// node IDs prior to mapping.
|
||||
type PseudoFunc func(srcNodeID string, srcNode RenderableNode, dstNodeID string) (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 report.NodeMetadata) (RenderableNode, bool) {
|
||||
// MapFunc is anything which can take an arbitrary RenderableNode and
|
||||
// return another RenderableNode.
|
||||
//
|
||||
// As with LeafMapFunc, if the final output parameter is false, the node
|
||||
// shall be omitted from the rendered topology.
|
||||
type MapFunc func(RenderableNode) (RenderableNode, bool)
|
||||
|
||||
// MapEndpointIdentity maps a endpoint topology node to endpoint RenderableNode
|
||||
// node. As it is only ever run on endpoint topology nodes, we can safely
|
||||
// assume the presence of certain keys.
|
||||
func MapEndpointIdentity(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var (
|
||||
identifier = fmt.Sprintf("%s:%s:%s", "pid", m["domain"], m["pid"])
|
||||
minor = fmt.Sprintf("%s (%s)", m["domain"], m["pid"])
|
||||
show = m["pid"] != "" && m["name"] != ""
|
||||
id = fmt.Sprintf("endpoint:%s:%s:%s", report.ExtractHostID(m), m["addr"], m["port"])
|
||||
major = fmt.Sprintf("%s:%s", m["addr"], m["port"])
|
||||
pid, ok = m["pid"]
|
||||
minor = report.ExtractHostID(m)
|
||||
rank = major
|
||||
)
|
||||
|
||||
return newRenderableNode(identifier, m["name"], minor, m["pid"]), show
|
||||
}
|
||||
|
||||
// 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 report.NodeMetadata) (RenderableNode, bool) {
|
||||
show := m["pid"] != "" && m["name"] != ""
|
||||
return newRenderableNode(m["name"], m["name"], "", m["name"]), show
|
||||
}
|
||||
|
||||
// MapEndpoint2Container maps endpoint topology nodes to the containers they run
|
||||
// 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 report.NodeMetadata) (RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_container_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
} else {
|
||||
id, major, minor, rank = m["docker_container_id"], "", m["domain"], ""
|
||||
if ok {
|
||||
minor = fmt.Sprintf("%s (%s)", report.ExtractHostID(m), pid)
|
||||
}
|
||||
|
||||
return newRenderableNode(id, major, minor, rank), true
|
||||
return NewRenderableNode(id, major, minor, rank, m), true
|
||||
}
|
||||
|
||||
// MapContainerIdentity maps container topology node to container mapped nodes.
|
||||
// MapProcessIdentity maps a process topology node to process RenderableNode node.
|
||||
// As it is only ever run on process topology nodes, we can safely assume the
|
||||
// presence of certain keys.
|
||||
func MapProcessIdentity(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var (
|
||||
id = fmt.Sprintf("pid:%s:%s", report.ExtractHostID(m), m["pid"])
|
||||
major = m["comm"]
|
||||
minor = fmt.Sprintf("%s (%s)", report.ExtractHostID(m), m["pid"])
|
||||
rank = m["pid"]
|
||||
)
|
||||
|
||||
return NewRenderableNode(id, major, minor, rank, m), true
|
||||
}
|
||||
|
||||
// MapContainerIdentity maps a container topology node to a container
|
||||
// RenderableNode node. As it is only ever run on container topology
|
||||
// nodes, we can safely assume the presences of certain keys.
|
||||
func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_container_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
} else {
|
||||
id, major, minor, rank = m["docker_container_id"], m["docker_container_name"], m["domain"], m["docker_image_id"]
|
||||
var (
|
||||
id = m["docker_container_id"]
|
||||
major = m["docker_container_name"]
|
||||
minor = report.ExtractHostID(m)
|
||||
rank = m["docker_image_id"]
|
||||
)
|
||||
|
||||
return NewRenderableNode(id, major, minor, rank, m), true
|
||||
}
|
||||
|
||||
// MapEndpoint2Process maps endpoint RenderableNodes to process
|
||||
// RenderableNodes.
|
||||
//
|
||||
// If this function is given a pseudo node, then it will just return it;
|
||||
// Pseudo nodes will never have pids in them, and therefore will never
|
||||
// be able to be turned into a Process node.
|
||||
//
|
||||
// Otherwise, this function will produce a node with the correct ID
|
||||
// format for a process, but without any Major or Minor labels.
|
||||
// It does not have enough info to do that, and the resulting graph
|
||||
// must be merged with a process graph to get that info.
|
||||
func MapEndpoint2Process(n RenderableNode) (RenderableNode, bool) {
|
||||
if n.Pseudo {
|
||||
return n, true
|
||||
}
|
||||
|
||||
return newRenderableNode(id, major, minor, rank), true
|
||||
pid, ok := n.NodeMetadata["pid"]
|
||||
if !ok {
|
||||
// TODO: Propogate a pseudo node instead of dropping this?
|
||||
return RenderableNode{}, false
|
||||
}
|
||||
|
||||
id := fmt.Sprintf("pid:%s:%s", report.ExtractHostID(n.NodeMetadata), pid)
|
||||
return newDerivedNode(id, n), true
|
||||
}
|
||||
|
||||
// MapProcess2Container maps process RenderableNodes to container
|
||||
// RenderableNodes.
|
||||
//
|
||||
// If this function is given a node without a docker_container_id
|
||||
// (including other pseudo nodes), it will produce an "Uncontained"
|
||||
// pseudo node.
|
||||
//
|
||||
// Otherwise, this function will produce a node with the correct ID
|
||||
// format for a container, but without any Major or Minor labels.
|
||||
// It does not have enough info to do that, and the resulting graph
|
||||
// must be merged with a container graph to get that info.
|
||||
func MapProcess2Container(n RenderableNode) (RenderableNode, bool) {
|
||||
id, ok := n.NodeMetadata["docker_container_id"]
|
||||
if !ok || n.Pseudo {
|
||||
return newDerivedPseudoNode(UncontainedID, UncontainedMajor, n), true
|
||||
}
|
||||
|
||||
return newDerivedNode(id, n), true
|
||||
}
|
||||
|
||||
// MapProcess2Name maps process RenderableNodes to RenderableNodes
|
||||
// for each process name.
|
||||
//
|
||||
// This mapper is unlike the other foo2bar mappers as the intention
|
||||
// is not to join the information with another topology. Therefore
|
||||
// it outputs a properly-formed node with labels etc.
|
||||
func MapProcess2Name(n RenderableNode) (RenderableNode, bool) {
|
||||
if n.Pseudo {
|
||||
return n, true
|
||||
}
|
||||
|
||||
name, ok := n.NodeMetadata["comm"]
|
||||
if !ok {
|
||||
// TODO: Propogate a pseudo node instead of dropping this?
|
||||
return RenderableNode{}, false
|
||||
}
|
||||
|
||||
node := newDerivedNode(name, n)
|
||||
node.LabelMajor = name
|
||||
node.Rank = name
|
||||
return node, true
|
||||
}
|
||||
|
||||
// ProcessContainerImage maps topology nodes to the container images they run
|
||||
@@ -103,12 +162,12 @@ func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
func ProcessContainerImage(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
var id, major, minor, rank string
|
||||
if m["docker_image_id"] == "" {
|
||||
id, major, minor, rank = "uncontained", "Uncontained", "", "uncontained"
|
||||
id, major, minor, rank = UncontainedID, UncontainedMajor, "", UncontainedID
|
||||
} else {
|
||||
id, major, minor, rank = m["docker_image_id"], m["docker_image_name"], "", m["docker_image_id"]
|
||||
}
|
||||
|
||||
return newRenderableNode(id, major, minor, rank), true
|
||||
return NewRenderableNode(id, major, minor, rank, m), true
|
||||
}
|
||||
|
||||
// NetworkHostname takes a node NodeMetadata and returns a representation
|
||||
@@ -125,7 +184,7 @@ func NetworkHostname(m report.NodeMetadata) (RenderableNode, bool) {
|
||||
domain = parts[1]
|
||||
}
|
||||
|
||||
return newRenderableNode(fmt.Sprintf("host:%s", name), parts[0], domain, parts[0]), name != ""
|
||||
return NewRenderableNode(fmt.Sprintf("host:%s", name), parts[0], domain, parts[0], m), name != ""
|
||||
}
|
||||
|
||||
// GenericPseudoNode contains heuristics for building sensible pseudo nodes.
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func TestUngroupedMapping(t *testing.T) {
|
||||
for i, c := range []struct {
|
||||
f render.MapFunc
|
||||
f render.LeafMapFunc
|
||||
id string
|
||||
meta report.NodeMetadata
|
||||
wantOK bool
|
||||
@@ -40,52 +40,6 @@ func TestUngroupedMapping(t *testing.T) {
|
||||
wantMinor: "",
|
||||
wantRank: "localhost",
|
||||
},
|
||||
{
|
||||
f: render.ProcessPID,
|
||||
id: "not-used-beta",
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
"name": "curl",
|
||||
"domain": "hosta",
|
||||
},
|
||||
wantOK: true,
|
||||
wantID: "pid:hosta:42",
|
||||
wantMajor: "curl",
|
||||
wantMinor: "hosta (42)",
|
||||
wantRank: "42",
|
||||
},
|
||||
{
|
||||
f: render.MapEndpoint2Container,
|
||||
id: "foo-id",
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
"name": "curl",
|
||||
"domain": "hosta",
|
||||
},
|
||||
wantOK: true,
|
||||
wantID: "uncontained",
|
||||
wantMajor: "Uncontained",
|
||||
wantMinor: "",
|
||||
wantRank: "uncontained",
|
||||
},
|
||||
{
|
||||
f: render.MapEndpoint2Container,
|
||||
id: "bar-id",
|
||||
meta: report.NodeMetadata{
|
||||
"pid": "42",
|
||||
"name": "curl",
|
||||
"domain": "hosta",
|
||||
"docker_container_id": "d321fe0",
|
||||
"docker_container_name": "walking_sparrow",
|
||||
"docker_image_id": "1101fff",
|
||||
"docker_image_name": "org/app:latest",
|
||||
},
|
||||
wantOK: true,
|
||||
wantID: "d321fe0",
|
||||
wantMajor: "",
|
||||
wantMinor: "hosta",
|
||||
wantRank: "",
|
||||
},
|
||||
} {
|
||||
identity := fmt.Sprintf("(%d %s %v)", i, c.id, c.meta)
|
||||
|
||||
|
||||
183
render/render.go
183
render/render.go
@@ -6,17 +6,42 @@ import (
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
// Renderer is something that can render a report to a set of RenderableNodes
|
||||
// Renderer is something that can render a report to a set of RenderableNodes.
|
||||
type Renderer interface {
|
||||
Render(report.Report) RenderableNodes
|
||||
AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata
|
||||
}
|
||||
|
||||
// Reduce renderer is a Renderer which merges together the output of several
|
||||
// other renderers
|
||||
// other renderers.
|
||||
type Reduce []Renderer
|
||||
|
||||
// Render produces a set of RenderableNodes given a Report
|
||||
// Map is a Renderer which produces a set of RenderableNodes from the set of
|
||||
// RenderableNodes produced by another Renderer.
|
||||
type Map struct {
|
||||
MapFunc
|
||||
Renderer
|
||||
}
|
||||
|
||||
// LeafMap is a Renderer which produces a set of RenderableNodes from a report.Topology
|
||||
// by using a map function and topology selector.
|
||||
type LeafMap struct {
|
||||
Selector report.TopologySelector
|
||||
Mapper LeafMapFunc
|
||||
Pseudo PseudoFunc
|
||||
}
|
||||
|
||||
// FilterUnconnected is a Renderer which filters out unconnected nodes.
|
||||
type FilterUnconnected struct {
|
||||
Renderer
|
||||
}
|
||||
|
||||
// MakeReduce is the only sane way to produce a Reduce Renderer.
|
||||
func MakeReduce(renderers ...Renderer) Renderer {
|
||||
return Reduce(renderers)
|
||||
}
|
||||
|
||||
// Render produces a set of RenderableNodes given a Report.
|
||||
func (r Reduce) Render(rpt report.Report) RenderableNodes {
|
||||
result := RenderableNodes{}
|
||||
for _, renderer := range r {
|
||||
@@ -25,7 +50,7 @@ func (r Reduce) Render(rpt report.Report) RenderableNodes {
|
||||
return result
|
||||
}
|
||||
|
||||
// AggregateMetadata produces an AggregateMetadata for a given edge
|
||||
// AggregateMetadata produces an AggregateMetadata for a given edge.
|
||||
func (r Reduce) AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata {
|
||||
metadata := report.AggregateMetadata{}
|
||||
for _, renderer := range r {
|
||||
@@ -34,37 +59,100 @@ func (r Reduce) AggregateMetadata(rpt report.Report, localID, remoteID string) r
|
||||
return metadata
|
||||
}
|
||||
|
||||
// Map is a Renderer which produces a set of RendererNodes by using a
|
||||
// Mapper functions and topology selector.
|
||||
type Map struct {
|
||||
Selector report.TopologySelector
|
||||
Mapper MapFunc
|
||||
Pseudo PseudoFunc
|
||||
}
|
||||
|
||||
// Render produces a set of RenderableNodes given a Report
|
||||
// Render transforms a set of RenderableNodes produces by another Renderer.
|
||||
// using a map function
|
||||
func (m Map) Render(rpt report.Report) RenderableNodes {
|
||||
return Topology(m.Selector(rpt), m.Mapper, m.Pseudo)
|
||||
output, _ := m.render(rpt)
|
||||
return output
|
||||
}
|
||||
|
||||
// Topology transforms a given Topology into a set of RenderableNodes, which
|
||||
func (m Map) render(rpt report.Report) (RenderableNodes, map[string]string) {
|
||||
input := m.Renderer.Render(rpt)
|
||||
output := RenderableNodes{}
|
||||
mapped := map[string]string{} // input node ID -> output node ID
|
||||
adjacencies := map[string]report.IDList{} // output node ID -> input node Adjacencies
|
||||
|
||||
for _, inRenderable := range input {
|
||||
outRenderable, ok := m.MapFunc(inRenderable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
existing, ok := output[outRenderable.ID]
|
||||
if ok {
|
||||
outRenderable.Merge(existing)
|
||||
}
|
||||
|
||||
output[outRenderable.ID] = outRenderable
|
||||
mapped[inRenderable.ID] = outRenderable.ID
|
||||
adjacencies[outRenderable.ID] = adjacencies[outRenderable.ID].Add(inRenderable.Adjacency...)
|
||||
}
|
||||
|
||||
// Rewrite Adjacency for new node IDs.
|
||||
// NB we don't do pseudo nodes here; we assume the input graph
|
||||
// is properly-connected, and if the map func dropped a node,
|
||||
// we drop links to it.
|
||||
for outNodeID, inAdjacency := range adjacencies {
|
||||
outAdjacency := report.MakeIDList()
|
||||
for _, inAdjacent := range inAdjacency {
|
||||
if outAdjacent, ok := mapped[inAdjacent]; ok {
|
||||
outAdjacency = outAdjacency.Add(outAdjacent)
|
||||
}
|
||||
}
|
||||
outNode := output[outNodeID]
|
||||
outNode.Adjacency = outAdjacency
|
||||
output[outNodeID] = outNode
|
||||
}
|
||||
|
||||
return output, mapped
|
||||
}
|
||||
|
||||
// AggregateMetadata 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 (m Map) AggregateMetadata(rpt report.Report, srcRenderableID, dstRenderableID string) report.AggregateMetadata {
|
||||
// First we need to map the ids in this layer into the ids in the underlying layer
|
||||
_, mapped := m.render(rpt) // this maps from old -> new
|
||||
inverted := map[string][]string{} // this maps from new -> old(s)
|
||||
for k, v := range mapped {
|
||||
existing := inverted[v]
|
||||
existing = append(existing, k)
|
||||
inverted[v] = existing
|
||||
}
|
||||
|
||||
// Now work out a slice of edges this edge is constructed from
|
||||
oldEdges := []struct{ src, dst string }{}
|
||||
for _, oldSrcID := range inverted[srcRenderableID] {
|
||||
for _, oldDstID := range inverted[dstRenderableID] {
|
||||
oldEdges = append(oldEdges, struct{ src, dst string }{oldSrcID, oldDstID})
|
||||
}
|
||||
}
|
||||
|
||||
// Now recurse for each old edge
|
||||
output := report.AggregateMetadata{}
|
||||
for _, edge := range oldEdges {
|
||||
metadata := m.Renderer.AggregateMetadata(rpt, edge.src, edge.dst)
|
||||
output.Merge(metadata)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// Render transforms a given Report 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 Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) RenderableNodes {
|
||||
// Nodes with the same mapped IDs will be merged.
|
||||
func (m LeafMap) Render(rpt report.Report) RenderableNodes {
|
||||
t := m.Selector(rpt)
|
||||
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
|
||||
)
|
||||
source2mapped := map[string]string{} // source node ID -> mapped node ID
|
||||
for nodeID, metadata := range t.NodeMetadatas {
|
||||
mapped, ok := mapFunc(metadata)
|
||||
mapped, ok := m.Mapper(metadata)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -77,18 +165,19 @@ func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) Rendera
|
||||
mapped.Merge(existing)
|
||||
}
|
||||
|
||||
mapped.Origins = mapped.Origins.Add(nodeID)
|
||||
origins := mapped.Origins
|
||||
origins = origins.Add(nodeID)
|
||||
origins = origins.Add(metadata[report.HostNodeID])
|
||||
mapped.Origins = origins
|
||||
|
||||
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]
|
||||
srcNodeID, ok = report.ParseAdjacencyID(src)
|
||||
srcRenderableID = source2mapped[srcNodeID] // must exist
|
||||
srcRenderableNode = nodes[srcRenderableID] // must exist
|
||||
)
|
||||
@@ -100,7 +189,7 @@ func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) Rendera
|
||||
for _, dstNodeID := range dsts {
|
||||
dstRenderableID, ok := source2mapped[dstNodeID]
|
||||
if !ok {
|
||||
pseudoNode, ok := pseudoFunc(srcNodeID, srcRenderableNode, dstNodeID)
|
||||
pseudoNode, ok := m.Pseudo(srcNodeID, srcRenderableNode, dstNodeID)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -110,11 +199,10 @@ func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) Rendera
|
||||
}
|
||||
|
||||
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())
|
||||
srcRenderableNode.AggregateMetadata.Merge(md.Transform())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,16 +212,12 @@ func Topology(t report.Topology, mapFunc MapFunc, pseudoFunc PseudoFunc) Rendera
|
||||
return nodes
|
||||
}
|
||||
|
||||
// AggregateMetadata produces an AggregateMetadata for a given edge
|
||||
func (m Map) AggregateMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata {
|
||||
return edgeMetadata(m.Selector(rpt), m.Mapper, localID, remoteID).Transform()
|
||||
}
|
||||
|
||||
// EdgeMetadata gives the metadata of an edge from the perspective of the
|
||||
// AggregateMetadata 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 {
|
||||
func (m LeafMap) AggregateMetadata(rpt report.Report, srcRenderableID, dstRenderableID string) report.AggregateMetadata {
|
||||
t := m.Selector(rpt)
|
||||
metadata := report.EdgeMetadata{}
|
||||
for edgeID, edgeMeta := range t.EdgeMetadatas {
|
||||
src, dst, ok := report.ParseEdgeID(edgeID)
|
||||
@@ -142,16 +226,33 @@ func edgeMetadata(t report.Topology, mapFunc MapFunc, srcRenderableID, dstRender
|
||||
continue
|
||||
}
|
||||
if src != report.TheInternet {
|
||||
mapped, _ := mapFunc(t.NodeMetadatas[src])
|
||||
mapped, _ := m.Mapper(t.NodeMetadatas[src])
|
||||
src = mapped.ID
|
||||
}
|
||||
if dst != report.TheInternet {
|
||||
mapped, _ := mapFunc(t.NodeMetadatas[dst])
|
||||
mapped, _ := m.Mapper(t.NodeMetadatas[dst])
|
||||
dst = mapped.ID
|
||||
}
|
||||
if src == srcRenderableID && dst == dstRenderableID {
|
||||
metadata.Flatten(edgeMeta)
|
||||
}
|
||||
}
|
||||
return metadata
|
||||
return metadata.Transform()
|
||||
}
|
||||
|
||||
// Render produces a set of RenderableNodes given a Report
|
||||
func (f FilterUnconnected) Render(rpt report.Report) RenderableNodes {
|
||||
input := f.Renderer.Render(rpt)
|
||||
output := RenderableNodes{}
|
||||
for id, node := range input {
|
||||
if len(node.Adjacency) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
output[id] = node
|
||||
for _, id := range node.Adjacency {
|
||||
output[id] = input[id]
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -6,15 +6,8 @@ import (
|
||||
|
||||
"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 {
|
||||
render.RenderableNodes
|
||||
aggregateMetadata report.AggregateMetadata
|
||||
@@ -55,360 +48,121 @@ func TestReduceEdge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
clientHostID = "client.hostname.com"
|
||||
serverHostID = "server.hostname.com"
|
||||
randomHostID = "random.hostname.com"
|
||||
unknownHostID = ""
|
||||
func TestMapRender1(t *testing.T) {
|
||||
// 1. Check when we return false, the node gets filtered out
|
||||
mapper := render.Map{
|
||||
MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) {
|
||||
return render.RenderableNode{}, false
|
||||
},
|
||||
Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
|
||||
"foo": {ID: "foo"},
|
||||
}},
|
||||
}
|
||||
want := render.RenderableNodes{}
|
||||
have := mapper.Render(report.MakeReport())
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("want %+v, have %+v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
clientHostName = clientHostID
|
||||
serverHostName = serverHostID
|
||||
func TestMapRender2(t *testing.T) {
|
||||
// 2. Check we can remap two nodes into one
|
||||
mapper := render.Map{
|
||||
MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) {
|
||||
return render.RenderableNode{ID: "bar"}, true
|
||||
},
|
||||
Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
|
||||
"foo": {ID: "foo"},
|
||||
"baz": {ID: "baz"},
|
||||
}},
|
||||
}
|
||||
want := render.RenderableNodes{
|
||||
"bar": render.RenderableNode{ID: "bar"},
|
||||
}
|
||||
have := mapper.Render(report.MakeReport())
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("want %+v, have %+v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
clientHostNodeID = report.MakeHostNodeID(clientHostID)
|
||||
serverHostNodeID = report.MakeHostNodeID(serverHostID)
|
||||
randomHostNodeID = report.MakeHostNodeID(randomHostID)
|
||||
func TestMapRender3(t *testing.T) {
|
||||
// 3. Check we can remap adjacencies
|
||||
mapper := render.Map{
|
||||
MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) {
|
||||
return render.RenderableNode{ID: "_" + nodes.ID}, true
|
||||
},
|
||||
Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
|
||||
"foo": {ID: "foo", Adjacency: report.MakeIDList("baz")},
|
||||
"baz": {ID: "baz", Adjacency: report.MakeIDList("foo")},
|
||||
}},
|
||||
}
|
||||
want := render.RenderableNodes{
|
||||
"_foo": {ID: "_foo", Adjacency: report.MakeIDList("_baz")},
|
||||
"_baz": {ID: "_baz", Adjacency: report.MakeIDList("_foo")},
|
||||
}
|
||||
have := mapper.Render(report.MakeReport())
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Errorf("want %+v, have %+v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
client54001NodeID = report.MakeEndpointNodeID(clientHostID, "10.10.10.20", "54001") // curl (1)
|
||||
client54002NodeID = 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
|
||||
|
||||
clientAddressNodeID = report.MakeAddressNodeID(clientHostID, "10.10.10.20")
|
||||
serverAddressNodeID = report.MakeAddressNodeID(serverHostID, "192.168.1.1")
|
||||
randomAddressNodeID = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology
|
||||
unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, "10.10.10.10")
|
||||
)
|
||||
|
||||
var (
|
||||
rpt = report.Report{
|
||||
Endpoint: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80),
|
||||
report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80),
|
||||
report.MakeAdjacencyID(server80): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1, unknownClient2, unknownClient3),
|
||||
},
|
||||
func TestMapEdge(t *testing.T) {
|
||||
selector := func(_ report.Report) report.Topology {
|
||||
return report.Topology{
|
||||
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 :)
|
||||
client54001NodeID: report.NodeMetadata{
|
||||
"name": "curl",
|
||||
"domain": "client-54001-domain",
|
||||
"pid": "10001",
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
"host_name": clientHostName,
|
||||
},
|
||||
client54002NodeID: 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,
|
||||
},
|
||||
"foo": report.NodeMetadata{"id": "foo"},
|
||||
"bar": report.NodeMetadata{"id": "bar"},
|
||||
},
|
||||
Adjacency: report.Adjacency{
|
||||
">foo": report.MakeIDList("bar"),
|
||||
">bar": report.MakeIDList("foo"),
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{
|
||||
report.MakeEdgeID(client54001NodeID, server80): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 100,
|
||||
BytesEgress: 10,
|
||||
},
|
||||
report.MakeEdgeID(client54002NodeID, server80): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 200,
|
||||
BytesEgress: 20,
|
||||
},
|
||||
"foo|bar": report.EdgeMetadata{WithBytes: true, BytesIngress: 1, BytesEgress: 2},
|
||||
"bar|foo": report.EdgeMetadata{WithBytes: true, BytesIngress: 3, BytesEgress: 4},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
report.MakeEdgeID(server80, client54001NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 10,
|
||||
BytesEgress: 100,
|
||||
},
|
||||
report.MakeEdgeID(server80, client54002NodeID): 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,
|
||||
},
|
||||
},
|
||||
},
|
||||
Process: report.Topology{
|
||||
Adjacency: report.Adjacency{},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
report.MakeProcessNodeID(clientHostID, "4242"): report.NodeMetadata{
|
||||
"host_name": "client.host.com",
|
||||
"pid": "4242",
|
||||
"comm": "curl",
|
||||
"docker_container_id": "a1b2c3d4e5",
|
||||
"docker_container_name": "fixture-container",
|
||||
"docker_image_id": "0000000000",
|
||||
"docker_image_name": "fixture/container:latest",
|
||||
},
|
||||
report.MakeProcessNodeID(serverHostID, "215"): report.NodeMetadata{
|
||||
"pid": "215",
|
||||
"process_name": "apache",
|
||||
},
|
||||
identity := func(nmd report.NodeMetadata) (render.RenderableNode, bool) {
|
||||
return render.NewRenderableNode(nmd["id"], "", "", "", nmd), true
|
||||
}
|
||||
|
||||
"no-container": report.NodeMetadata{},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{},
|
||||
mapper := render.Map{
|
||||
MapFunc: func(nodes render.RenderableNode) (render.RenderableNode, bool) {
|
||||
return render.RenderableNode{ID: "_" + nodes.ID}, true
|
||||
},
|
||||
Address: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(clientAddressNodeID): report.MakeIDList(serverAddressNodeID),
|
||||
report.MakeAdjacencyID(randomAddressNodeID): report.MakeIDList(serverAddressNodeID),
|
||||
report.MakeAdjacencyID(serverAddressNodeID): report.MakeIDList(clientAddressNodeID, unknownAddressNodeID), // no backlink to random
|
||||
},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
clientAddressNodeID: report.NodeMetadata{
|
||||
"name": "client.hostname.com", // hostname
|
||||
"host_name": "client.hostname.com",
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
randomAddressNodeID: report.NodeMetadata{
|
||||
"name": "random.hostname.com", // hostname
|
||||
report.HostNodeID: randomHostNodeID,
|
||||
},
|
||||
serverAddressNodeID: report.NodeMetadata{
|
||||
"name": "server.hostname.com", // hostname
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{
|
||||
report.MakeEdgeID(clientAddressNodeID, serverAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
report.MakeEdgeID(randomAddressNodeID, serverAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 20, // dangling connections, weird but possible
|
||||
},
|
||||
report.MakeEdgeID(serverAddressNodeID, clientAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
report.MakeEdgeID(serverAddressNodeID, unknownAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
Host: report.Topology{
|
||||
Adjacency: report.Adjacency{},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
serverHostNodeID: report.NodeMetadata{
|
||||
"host_name": serverHostName,
|
||||
"local_networks": "10.10.10.0/24",
|
||||
"os": "Linux",
|
||||
"load": "0.01 0.01 0.01",
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{},
|
||||
Renderer: render.LeafMap{
|
||||
Selector: selector,
|
||||
Mapper: identity,
|
||||
Pseudo: nil,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestRenderByEndpointPID(t *testing.T) {
|
||||
want := render.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{},
|
||||
},
|
||||
want := report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 1,
|
||||
report.KeyBytesEgress: 2,
|
||||
}
|
||||
have := render.Topology(rpt.Endpoint, render.ProcessPID, render.GenericPseudoNode)
|
||||
have := mapper.AggregateMetadata(report.MakeReport(), "_foo", "_bar")
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
t.Errorf("want %+v, have %+v", 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.
|
||||
func TestFilterRender(t *testing.T) {
|
||||
renderer := render.FilterUnconnected{
|
||||
Renderer: mockRenderer{RenderableNodes: render.RenderableNodes{
|
||||
"foo": {ID: "foo", Adjacency: report.MakeIDList("bar")},
|
||||
"bar": {ID: "bar", Adjacency: report.MakeIDList("foo")},
|
||||
"baz": {ID: "baz", Adjacency: report.MakeIDList()},
|
||||
}},
|
||||
}
|
||||
want := render.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{},
|
||||
},
|
||||
"foo": {ID: "foo", Adjacency: report.MakeIDList("bar")},
|
||||
"bar": {ID: "bar", Adjacency: report.MakeIDList("foo")},
|
||||
}
|
||||
have := render.Topology(rpt.Endpoint, render.ProcessName, render.GenericGroupedPseudoNode)
|
||||
have := renderer.Render(report.MakeReport())
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
t.Errorf("want %+v, have %+v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderByNetworkHostname(t *testing.T) {
|
||||
want := render.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 := render.Topology(rpt.Address, render.NetworkHostname, render.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
|
||||
}
|
||||
|
||||
@@ -8,14 +8,16 @@ import (
|
||||
// an element of a topology. It should contain information that's relevant
|
||||
// to rendering a node when there are many nodes visible at once.
|
||||
type RenderableNode struct {
|
||||
ID string `json:"id"` //
|
||||
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
|
||||
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
|
||||
Rank string `json:"rank"` // to help the layout engine
|
||||
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
|
||||
Adjacency report.IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain)
|
||||
Origins report.IDList `json:"origins,omitempty"` // Core node IDs that contributed information
|
||||
Metadata report.AggregateMetadata `json:"metadata"` // Numeric sums
|
||||
ID string `json:"id"` //
|
||||
LabelMajor string `json:"label_major"` // e.g. "process", human-readable
|
||||
LabelMinor string `json:"label_minor,omitempty"` // e.g. "hostname", human-readable, optional
|
||||
Rank string `json:"rank"` // to help the layout engine
|
||||
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
|
||||
Adjacency report.IDList `json:"adjacency,omitempty"` // Node IDs (in the same topology domain)
|
||||
Origins report.IDList `json:"origins,omitempty"` // Core node IDs that contributed information
|
||||
|
||||
report.AggregateMetadata `json:"metadata"` // Numeric sums
|
||||
report.NodeMetadata `json:"-"` // merged NodeMetadata of the nodes used to build this
|
||||
}
|
||||
|
||||
// RenderableNodes is a set of RenderableNodes
|
||||
@@ -54,5 +56,57 @@ func (rn *RenderableNode) Merge(other RenderableNode) {
|
||||
rn.Adjacency = rn.Adjacency.Add(other.Adjacency...)
|
||||
rn.Origins = rn.Origins.Add(other.Origins...)
|
||||
|
||||
rn.Metadata.Merge(other.Metadata)
|
||||
rn.AggregateMetadata.Merge(other.AggregateMetadata)
|
||||
rn.NodeMetadata.Merge(other.NodeMetadata)
|
||||
}
|
||||
|
||||
// NewRenderableNode makes a new RenderableNode
|
||||
func NewRenderableNode(id, major, minor, rank string, nmd report.NodeMetadata) RenderableNode {
|
||||
return RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: minor,
|
||||
Rank: rank,
|
||||
Pseudo: false,
|
||||
AggregateMetadata: report.AggregateMetadata{},
|
||||
NodeMetadata: nmd.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
func newDerivedNode(id string, node RenderableNode) RenderableNode {
|
||||
return RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: "",
|
||||
LabelMinor: "",
|
||||
Rank: "",
|
||||
Pseudo: node.Pseudo,
|
||||
AggregateMetadata: node.AggregateMetadata,
|
||||
Origins: node.Origins,
|
||||
NodeMetadata: report.NodeMetadata{},
|
||||
}
|
||||
}
|
||||
|
||||
func newPseudoNode(id, major, minor string) RenderableNode {
|
||||
return RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: minor,
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
AggregateMetadata: report.AggregateMetadata{},
|
||||
NodeMetadata: report.NodeMetadata{},
|
||||
}
|
||||
}
|
||||
|
||||
func newDerivedPseudoNode(id, major string, node RenderableNode) RenderableNode {
|
||||
return RenderableNode{
|
||||
ID: id,
|
||||
LabelMajor: major,
|
||||
LabelMinor: "",
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
AggregateMetadata: node.AggregateMetadata,
|
||||
Origins: node.Origins,
|
||||
NodeMetadata: report.NodeMetadata{},
|
||||
}
|
||||
}
|
||||
|
||||
47
render/topologies.go
Normal file
47
render/topologies.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
// EndpointRenderer is a Renderer which produces a renderable endpoint graph.
|
||||
var EndpointRenderer = LeafMap{
|
||||
Selector: report.SelectEndpoint,
|
||||
Mapper: MapEndpointIdentity,
|
||||
Pseudo: GenericPseudoNode,
|
||||
}
|
||||
|
||||
// ProcessRenderer is a Renderer which produces a renderable process
|
||||
// graph by merging the endpoint graph and the process topology.
|
||||
var ProcessRenderer = MakeReduce(
|
||||
Map{
|
||||
MapFunc: MapEndpoint2Process,
|
||||
Renderer: EndpointRenderer,
|
||||
},
|
||||
LeafMap{
|
||||
Selector: report.SelectProcess,
|
||||
Mapper: MapProcessIdentity,
|
||||
Pseudo: GenericPseudoNode,
|
||||
},
|
||||
)
|
||||
|
||||
// ProcessRenderer is a Renderer which produces a renderable process
|
||||
// name graph by munging the progess graph.
|
||||
var ProcessNameRenderer = Map{
|
||||
MapFunc: MapProcess2Name,
|
||||
Renderer: ProcessRenderer,
|
||||
}
|
||||
|
||||
// ContainerRenderer is a Renderer which produces a renderable container
|
||||
// graph by merging the process graph and the container topology.
|
||||
var ContainerRenderer = MakeReduce(
|
||||
Map{
|
||||
MapFunc: MapProcess2Container,
|
||||
Renderer: ProcessRenderer,
|
||||
},
|
||||
LeafMap{
|
||||
Selector: report.SelectContainer,
|
||||
Mapper: MapContainerIdentity,
|
||||
Pseudo: GenericPseudoNode,
|
||||
},
|
||||
)
|
||||
490
render/topologies_test.go
Normal file
490
render/topologies_test.go
Normal file
@@ -0,0 +1,490 @@
|
||||
package render_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
func init() {
|
||||
spew.Config.SortKeys = true // :\
|
||||
}
|
||||
|
||||
var (
|
||||
clientHostID = "client.hostname.com"
|
||||
serverHostID = "server.hostname.com"
|
||||
randomHostID = "random.hostname.com"
|
||||
unknownHostID = ""
|
||||
|
||||
clientIP = "10.10.10.20"
|
||||
serverIP = "192.168.1.1"
|
||||
clientPort54001 = "54001"
|
||||
clientPort54002 = "54002"
|
||||
serverPort = "80"
|
||||
|
||||
clientHostName = clientHostID
|
||||
serverHostName = serverHostID
|
||||
|
||||
clientPID = "10001"
|
||||
serverPID = "215"
|
||||
nonContainerPID = "1234"
|
||||
|
||||
clientHostNodeID = report.MakeHostNodeID(clientHostID)
|
||||
serverHostNodeID = report.MakeHostNodeID(serverHostID)
|
||||
randomHostNodeID = report.MakeHostNodeID(randomHostID)
|
||||
|
||||
client54001NodeID = report.MakeEndpointNodeID(clientHostID, clientIP, clientPort54001) // curl (1)
|
||||
client54002NodeID = report.MakeEndpointNodeID(clientHostID, clientIP, clientPort54002) // curl (2)
|
||||
unknownClient1NodeID = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54010") // we want to ensure two unknown clients, connnected
|
||||
unknownClient2NodeID = report.MakeEndpointNodeID(serverHostID, "10.10.10.10", "54020") // to the same server, are deduped.
|
||||
unknownClient3NodeID = report.MakeEndpointNodeID(serverHostID, "10.10.10.11", "54020") // Check this one isn't deduped
|
||||
server80NodeID = report.MakeEndpointNodeID(serverHostID, serverIP, serverPort) // apache
|
||||
|
||||
clientAddressNodeID = report.MakeAddressNodeID(clientHostID, "10.10.10.20")
|
||||
serverAddressNodeID = report.MakeAddressNodeID(serverHostID, "192.168.1.1")
|
||||
randomAddressNodeID = report.MakeAddressNodeID(randomHostID, "172.16.11.9") // only in Address topology
|
||||
unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, "10.10.10.10")
|
||||
|
||||
clientProcessNodeID = report.MakeProcessNodeID(clientHostID, clientPID)
|
||||
serverProcessNodeID = report.MakeProcessNodeID(serverHostID, serverPID)
|
||||
nonContainerProcessNodeID = report.MakeProcessNodeID(serverHostID, nonContainerPID)
|
||||
|
||||
clientContainerID = "a1b2c3d4e5"
|
||||
serverContainerID = "5e4d3c2b1a"
|
||||
clientContainerNodeID = report.MakeContainerNodeID(clientHostID, clientContainerID)
|
||||
serverContainerNodeID = report.MakeContainerNodeID(serverHostID, serverContainerID)
|
||||
)
|
||||
|
||||
var (
|
||||
rpt = report.Report{
|
||||
Endpoint: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(client54001NodeID): report.MakeIDList(server80NodeID),
|
||||
report.MakeAdjacencyID(client54002NodeID): report.MakeIDList(server80NodeID),
|
||||
report.MakeAdjacencyID(server80NodeID): report.MakeIDList(client54001NodeID, client54002NodeID, unknownClient1NodeID, unknownClient2NodeID, unknownClient3NodeID),
|
||||
},
|
||||
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 :)
|
||||
client54001NodeID: report.NodeMetadata{
|
||||
"addr": clientIP,
|
||||
"port": clientPort54001,
|
||||
"pid": clientPID,
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
client54002NodeID: report.NodeMetadata{
|
||||
"addr": clientIP,
|
||||
"port": clientPort54002,
|
||||
"pid": clientPID, // should be same as above!
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
server80NodeID: report.NodeMetadata{
|
||||
"addr": serverIP,
|
||||
"port": serverPort,
|
||||
"pid": serverPID,
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{
|
||||
report.MakeEdgeID(client54001NodeID, server80NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 100,
|
||||
BytesEgress: 10,
|
||||
},
|
||||
report.MakeEdgeID(client54002NodeID, server80NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 200,
|
||||
BytesEgress: 20,
|
||||
},
|
||||
|
||||
report.MakeEdgeID(server80NodeID, client54001NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 10,
|
||||
BytesEgress: 100,
|
||||
},
|
||||
report.MakeEdgeID(server80NodeID, client54002NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 20,
|
||||
BytesEgress: 200,
|
||||
},
|
||||
report.MakeEdgeID(server80NodeID, unknownClient1NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 30,
|
||||
BytesEgress: 300,
|
||||
},
|
||||
report.MakeEdgeID(server80NodeID, unknownClient2NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 40,
|
||||
BytesEgress: 400,
|
||||
},
|
||||
report.MakeEdgeID(server80NodeID, unknownClient3NodeID): report.EdgeMetadata{
|
||||
WithBytes: true,
|
||||
BytesIngress: 50,
|
||||
BytesEgress: 500,
|
||||
},
|
||||
},
|
||||
},
|
||||
Process: report.Topology{
|
||||
Adjacency: report.Adjacency{},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
clientProcessNodeID: report.NodeMetadata{
|
||||
"pid": clientPID,
|
||||
"comm": "curl",
|
||||
"docker_container_id": clientContainerID,
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
serverProcessNodeID: report.NodeMetadata{
|
||||
"pid": serverPID,
|
||||
"comm": "apache",
|
||||
"docker_container_id": serverContainerID,
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
nonContainerProcessNodeID: report.NodeMetadata{
|
||||
"pid": nonContainerPID,
|
||||
"comm": "bash",
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{},
|
||||
},
|
||||
Container: report.Topology{
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
clientContainerNodeID: report.NodeMetadata{
|
||||
"docker_container_id": clientContainerID,
|
||||
"docker_container_name": "client",
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
serverContainerNodeID: report.NodeMetadata{
|
||||
"docker_container_id": serverContainerID,
|
||||
"docker_container_name": "server",
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
},
|
||||
Address: report.Topology{
|
||||
Adjacency: report.Adjacency{
|
||||
report.MakeAdjacencyID(clientAddressNodeID): report.MakeIDList(serverAddressNodeID),
|
||||
report.MakeAdjacencyID(randomAddressNodeID): report.MakeIDList(serverAddressNodeID),
|
||||
report.MakeAdjacencyID(serverAddressNodeID): report.MakeIDList(clientAddressNodeID, unknownAddressNodeID), // no backlink to random
|
||||
},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
clientAddressNodeID: report.NodeMetadata{
|
||||
"name": "client.hostname.com", // hostname
|
||||
"host_name": "client.hostname.com",
|
||||
report.HostNodeID: clientHostNodeID,
|
||||
},
|
||||
randomAddressNodeID: report.NodeMetadata{
|
||||
"name": "random.hostname.com", // hostname
|
||||
report.HostNodeID: randomHostNodeID,
|
||||
},
|
||||
serverAddressNodeID: report.NodeMetadata{
|
||||
"name": "server.hostname.com", // hostname
|
||||
report.HostNodeID: serverHostNodeID,
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{
|
||||
report.MakeEdgeID(clientAddressNodeID, serverAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
report.MakeEdgeID(randomAddressNodeID, serverAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 20, // dangling connections, weird but possible
|
||||
},
|
||||
report.MakeEdgeID(serverAddressNodeID, clientAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 3,
|
||||
},
|
||||
report.MakeEdgeID(serverAddressNodeID, unknownAddressNodeID): report.EdgeMetadata{
|
||||
WithConnCountTCP: true,
|
||||
MaxConnCountTCP: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
Host: report.Topology{
|
||||
Adjacency: report.Adjacency{},
|
||||
NodeMetadatas: report.NodeMetadatas{
|
||||
serverHostNodeID: report.NodeMetadata{
|
||||
"host_name": serverHostName,
|
||||
"local_networks": "10.10.10.0/24",
|
||||
"os": "Linux",
|
||||
"load": "0.01 0.01 0.01",
|
||||
},
|
||||
},
|
||||
EdgeMetadatas: report.EdgeMetadatas{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := rpt.Validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func trimNodeMetadata(rns render.RenderableNodes) render.RenderableNodes {
|
||||
result := render.RenderableNodes{}
|
||||
for id, rn := range rns {
|
||||
rn.NodeMetadata = nil
|
||||
result[id] = rn
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func TestProcessRenderer(t *testing.T) {
|
||||
var (
|
||||
clientProcessID = fmt.Sprintf("pid:%s:%s", clientHostID, clientPID)
|
||||
serverProcessID = fmt.Sprintf("pid:%s:%s", serverHostID, serverPID)
|
||||
nonContainerProcessID = fmt.Sprintf("pid:%s:%s", serverHostID, nonContainerPID)
|
||||
)
|
||||
|
||||
want := render.RenderableNodes{
|
||||
clientProcessID: {
|
||||
ID: clientProcessID,
|
||||
LabelMajor: "curl",
|
||||
LabelMinor: fmt.Sprintf("%s (%s)", clientHostID, clientPID),
|
||||
Rank: clientPID,
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(serverProcessID),
|
||||
Origins: report.MakeIDList(client54001NodeID, client54002NodeID, clientProcessNodeID, clientHostNodeID),
|
||||
AggregateMetadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 300,
|
||||
report.KeyBytesEgress: 30,
|
||||
},
|
||||
},
|
||||
serverProcessID: {
|
||||
ID: serverProcessID,
|
||||
LabelMajor: "apache",
|
||||
LabelMinor: fmt.Sprintf("%s (%s)", serverHostID, serverPID),
|
||||
Rank: serverPID,
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(
|
||||
clientProcessID,
|
||||
"pseudo;10.10.10.10;192.168.1.1;80",
|
||||
"pseudo;10.10.10.11;192.168.1.1;80",
|
||||
),
|
||||
Origins: report.MakeIDList(server80NodeID, serverProcessNodeID, serverHostNodeID),
|
||||
AggregateMetadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 150,
|
||||
report.KeyBytesEgress: 1500,
|
||||
},
|
||||
},
|
||||
nonContainerProcessID: {
|
||||
ID: nonContainerProcessID,
|
||||
LabelMajor: "bash",
|
||||
LabelMinor: fmt.Sprintf("%s (%s)", serverHostID, nonContainerPID),
|
||||
Rank: nonContainerPID,
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(),
|
||||
Origins: report.MakeIDList(nonContainerProcessNodeID, serverHostNodeID),
|
||||
AggregateMetadata: report.AggregateMetadata{},
|
||||
},
|
||||
"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,
|
||||
AggregateMetadata: 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,
|
||||
AggregateMetadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := render.ProcessRenderer.Render(rpt)
|
||||
have = trimNodeMetadata(have)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessNameRenderer(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 := render.RenderableNodes{
|
||||
"curl": {
|
||||
ID: "curl",
|
||||
LabelMajor: "curl",
|
||||
LabelMinor: "",
|
||||
Rank: "curl",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList("apache"),
|
||||
Origins: report.MakeIDList(client54001NodeID, client54002NodeID, clientProcessNodeID, clientHostNodeID),
|
||||
AggregateMetadata: 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;192.168.1.1;80",
|
||||
"pseudo;10.10.10.11;192.168.1.1;80",
|
||||
),
|
||||
Origins: report.MakeIDList(server80NodeID, serverProcessNodeID, serverHostNodeID),
|
||||
AggregateMetadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 150,
|
||||
report.KeyBytesEgress: 1500,
|
||||
},
|
||||
},
|
||||
"bash": {
|
||||
ID: "bash",
|
||||
LabelMajor: "bash",
|
||||
LabelMinor: "",
|
||||
Rank: "bash",
|
||||
Pseudo: false,
|
||||
Origins: report.MakeIDList(nonContainerProcessNodeID, serverHostNodeID),
|
||||
AggregateMetadata: report.AggregateMetadata{},
|
||||
},
|
||||
"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,
|
||||
AggregateMetadata: 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,
|
||||
AggregateMetadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := render.ProcessNameRenderer.Render(rpt)
|
||||
have = trimNodeMetadata(have)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerRenderer(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 := render.RenderableNodes{
|
||||
clientContainerID: {
|
||||
ID: clientContainerID,
|
||||
LabelMajor: "client",
|
||||
LabelMinor: clientHostName,
|
||||
Rank: "",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(serverContainerID),
|
||||
Origins: report.MakeIDList(clientContainerNodeID, client54001NodeID, client54002NodeID, clientProcessNodeID, clientHostNodeID),
|
||||
AggregateMetadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 300,
|
||||
report.KeyBytesEgress: 30,
|
||||
},
|
||||
},
|
||||
serverContainerID: {
|
||||
ID: serverContainerID,
|
||||
LabelMajor: "server",
|
||||
LabelMinor: serverHostName,
|
||||
Rank: "",
|
||||
Pseudo: false,
|
||||
Adjacency: report.MakeIDList(clientContainerID, render.UncontainedID),
|
||||
Origins: report.MakeIDList(serverContainerNodeID, server80NodeID, serverProcessNodeID, serverHostNodeID),
|
||||
AggregateMetadata: report.AggregateMetadata{
|
||||
report.KeyBytesIngress: 150,
|
||||
report.KeyBytesEgress: 1500,
|
||||
},
|
||||
},
|
||||
render.UncontainedID: {
|
||||
ID: render.UncontainedID,
|
||||
LabelMajor: render.UncontainedMajor,
|
||||
LabelMinor: "",
|
||||
Rank: "",
|
||||
Pseudo: true,
|
||||
Origins: report.MakeIDList(nonContainerProcessNodeID, serverHostNodeID),
|
||||
AggregateMetadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := render.ContainerRenderer.Render(rpt)
|
||||
have = trimNodeMetadata(have)
|
||||
if !reflect.DeepEqual(want, have) {
|
||||
t.Error("\n" + diff(want, have))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderByNetworkHostname(t *testing.T) {
|
||||
want := render.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")),
|
||||
AggregateMetadata: 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")),
|
||||
AggregateMetadata: 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")),
|
||||
AggregateMetadata: 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,
|
||||
AggregateMetadata: report.AggregateMetadata{},
|
||||
},
|
||||
}
|
||||
have := render.LeafMap{
|
||||
Selector: report.SelectAddress,
|
||||
Mapper: render.NetworkHostname,
|
||||
Pseudo: render.GenericPseudoNode,
|
||||
}.Render(rpt)
|
||||
have = trimNodeMetadata(have)
|
||||
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
|
||||
}
|
||||
@@ -97,6 +97,12 @@ func ParseNodeID(nodeID string) (hostID string, remainder string, ok bool) {
|
||||
return fields[0], fields[1], true
|
||||
}
|
||||
|
||||
// ExtractHostID extracts the host id from NodeMetadata
|
||||
func ExtractHostID(m NodeMetadata) string {
|
||||
hostid, _, _ := ParseNodeID(m[HostNodeID])
|
||||
return hostid
|
||||
}
|
||||
|
||||
// MakePseudoNodeID produces a pseudo node ID from its composite parts.
|
||||
func MakePseudoNodeID(parts ...string) string {
|
||||
return strings.Join(append([]string{"pseudo"}, parts...), ScopeDelim)
|
||||
|
||||
@@ -53,6 +53,11 @@ func SelectEndpoint(r Report) Topology {
|
||||
return r.Endpoint
|
||||
}
|
||||
|
||||
// SelectProcess selects the process topology.
|
||||
func SelectProcess(r Report) Topology {
|
||||
return r.Process
|
||||
}
|
||||
|
||||
// SelectAddress selects the address topology.
|
||||
func SelectAddress(r Report) Topology {
|
||||
return r.Address
|
||||
|
||||
Reference in New Issue
Block a user