Merge pull request #185 from weaveworks/id-constructors

ID constructors
This commit is contained in:
Peter Bourgon
2015-06-08 18:06:21 +02:00
16 changed files with 431 additions and 192 deletions

View File

@@ -18,34 +18,34 @@ func (s StaticReport) Report() report.Report {
var testReport = report.Report{
Process: report.Topology{
Adjacency: report.Adjacency{
"hostA|;192.168.1.1;12345": []string{";192.168.1.2;80"},
"hostA|;192.168.1.1;12346": []string{";192.168.1.2;80"},
"hostA|;192.168.1.1;8888": []string{";1.2.3.4;22"},
"hostB|;192.168.1.2;80": []string{";192.168.1.1;12345"},
report.MakeAdjacencyID("hostA", report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345")): report.NewIDList(report.MakeEndpointNodeID("hostB", "192.168.1.2", "80")),
report.MakeAdjacencyID("hostA", report.MakeEndpointNodeID("hostA", "192.168.1.1", "12346")): report.NewIDList(report.MakeEndpointNodeID("hostB", "192.168.1.2", "80")),
report.MakeAdjacencyID("hostA", report.MakeEndpointNodeID("hostA", "192.168.1.1", "8888")): report.NewIDList(report.MakeEndpointNodeID("", "1.2.3.4", "22")),
report.MakeAdjacencyID("hostB", report.MakeEndpointNodeID("hostB", "192.168.1.2", "80")): report.NewIDList(report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345")),
},
EdgeMetadatas: report.EdgeMetadatas{
";192.168.1.1;12345|;192.168.1.2;80": report.EdgeMetadata{
report.MakeEdgeID(report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345"), report.MakeEndpointNodeID("hostB", "192.168.1.2", "80")): report.EdgeMetadata{
WithBytes: true,
BytesEgress: 12,
BytesIngress: 0,
WithConnCountTCP: true,
MaxConnCountTCP: 200,
},
";192.168.1.1;12346|;192.168.1.2;80": report.EdgeMetadata{
report.MakeEdgeID(report.MakeEndpointNodeID("hostA", "192.168.1.1", "12346"), report.MakeEndpointNodeID("hostB", "192.168.1.2", "80")): report.EdgeMetadata{
WithBytes: true,
BytesEgress: 12,
BytesIngress: 0,
WithConnCountTCP: true,
MaxConnCountTCP: 201,
},
";192.168.1.1;8888|;1.2.3.4;80": report.EdgeMetadata{
report.MakeEdgeID(report.MakeEndpointNodeID("hostA", "192.168.1.1", "8888"), report.MakeEndpointNodeID("", "1.2.3.4", "80")): report.EdgeMetadata{
WithBytes: true,
BytesEgress: 200,
BytesIngress: 0,
WithConnCountTCP: true,
MaxConnCountTCP: 202,
},
";192.168.1.2;80|;192.168.1.1;12345": report.EdgeMetadata{
report.MakeEdgeID(report.MakeEndpointNodeID("hostB", "192.168.1.2", "80"), report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345")): report.EdgeMetadata{
WithBytes: true,
BytesEgress: 0,
BytesIngress: 12,
@@ -54,22 +54,22 @@ func (s StaticReport) Report() report.Report {
},
},
NodeMetadatas: report.NodeMetadatas{
";192.168.1.1;12345": report.NodeMetadata{
report.MakeEndpointNodeID("hostA", "192.168.1.1", "12345"): report.NodeMetadata{
"pid": "23128",
"name": "curl",
"domain": "node-a.local",
},
";192.168.1.1;12346": report.NodeMetadata{ // <-- same as :12345
report.MakeEndpointNodeID("hostA", "192.168.1.1", "12346"): report.NodeMetadata{ // <-- same as :12345
"pid": "23128",
"name": "curl",
"domain": "node-a.local",
},
";192.168.1.1;8888": report.NodeMetadata{
report.MakeEndpointNodeID("hostA", "192.168.1.1", "8888"): report.NodeMetadata{
"pid": "55100",
"name": "ssh",
"domain": "node-a.local",
},
";192.168.1.2;80": report.NodeMetadata{
report.MakeEndpointNodeID("hostB", "192.168.1.2", "80"): report.NodeMetadata{
"pid": "215",
"name": "apache",
"domain": "node-b.local",
@@ -79,25 +79,25 @@ func (s StaticReport) Report() report.Report {
Network: report.Topology{
Adjacency: report.Adjacency{
"hostA|;192.168.1.1": []string{";192.168.1.2", ";1.2.3.4"},
"hostB|;192.168.1.2": []string{";192.168.1.1"},
report.MakeAdjacencyID("hostA", report.MakeAddressNodeID("hostA", "192.168.1.1")): report.NewIDList(report.MakeAddressNodeID("hostB", "192.168.1.2"), report.MakeAddressNodeID("", "1.2.3.4")),
report.MakeAdjacencyID("hostB", report.MakeAddressNodeID("hostB", "192.168.1.2")): report.NewIDList(report.MakeAddressNodeID("hostA", "192.168.1.1")),
},
EdgeMetadatas: report.EdgeMetadatas{
";192.168.1.1|;192.168.1.2": report.EdgeMetadata{
report.MakeEdgeID(report.MakeAddressNodeID("hostA", "192.168.1.1"), report.MakeAddressNodeID("hostB", "192.168.1.2")): report.EdgeMetadata{
WithBytes: true,
BytesEgress: 12,
BytesIngress: 0,
WithConnCountTCP: true,
MaxConnCountTCP: 14,
},
";192.168.1.1|;1.2.3.4": report.EdgeMetadata{
report.MakeEdgeID(report.MakeAddressNodeID("hostA", "192.168.1.1"), report.MakeAddressNodeID("", "1.2.3.4")): report.EdgeMetadata{
WithBytes: true,
BytesEgress: 200,
BytesIngress: 0,
WithConnCountTCP: true,
MaxConnCountTCP: 15,
},
";192.168.1.2|;192.168.1.1": report.EdgeMetadata{
report.MakeEdgeID(report.MakeAddressNodeID("hostB", "192.168.1.2"), report.MakeAddressNodeID("hostA", "192.168.1.1")): report.EdgeMetadata{
WithBytes: true,
BytesEgress: 0,
BytesIngress: 12,
@@ -106,10 +106,10 @@ func (s StaticReport) Report() report.Report {
},
},
NodeMetadatas: report.NodeMetadatas{
";192.168.1.1": report.NodeMetadata{
report.MakeAddressNodeID("hostA", "192.168.1.1"): report.NodeMetadata{
"name": "host-a",
},
";192.168.1.2": report.NodeMetadata{
report.MakeAddressNodeID("hostB", "192.168.1.2"): report.NodeMetadata{
"name": "host-b",
},
},

View File

@@ -132,7 +132,7 @@ func discover(c collector, p publisher, fixed []string) {
for _, adjacent := range r.Network.Adjacency {
for _, a := range adjacent {
ip := report.AddressIP(a) // address id -> IP
ip := report.AddressIDAddresser(a) // address id -> IP
if ip == nil {
continue
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"math/rand"
"net"
"strconv"
"time"
"github.com/weaveworks/scope/report"
@@ -56,14 +57,14 @@ func DemoReport(nodeCount int) report.Report {
src = hosts[rand.Intn(len(hosts))]
dst = hosts[rand.Intn(len(hosts))]
srcPort = rand.Intn(50000) + 10000
srcPortID = fmt.Sprintf("%s%s%s%d", report.ScopeDelim, src, report.ScopeDelim, srcPort)
dstPortID = fmt.Sprintf("%s%s%s%d", report.ScopeDelim, dst, report.ScopeDelim, c.dstPort)
srcID = "hostX" + report.IDDelim + srcPortID
dstID = "hostX" + report.IDDelim + dstPortID
srcAddressID = fmt.Sprintf("%s%s", report.ScopeDelim, src)
dstAddressID = fmt.Sprintf("%s%s", report.ScopeDelim, dst)
nodeSrcAddressID = "hostX" + report.IDDelim + srcAddressID
nodeDstAddressID = "hostX" + report.IDDelim + dstAddressID
srcPortID = report.MakeEndpointNodeID("", src, strconv.Itoa(srcPort))
dstPortID = report.MakeEndpointNodeID("", dst, strconv.Itoa(c.dstPort))
srcID = report.MakeAdjacencyID("hostX", srcPortID)
dstID = report.MakeAdjacencyID("hostX", dstPortID)
srcAddressID = report.MakeAddressNodeID("", src)
dstAddressID = report.MakeAddressNodeID("", dst)
nodeSrcAddressID = report.MakeAdjacencyID("hostX", srcAddressID)
nodeDstAddressID = report.MakeAdjacencyID("hostX", dstAddressID)
)
// Process topology
@@ -84,8 +85,8 @@ func DemoReport(nodeCount int) report.Report {
}
r.Process.Adjacency[dstID] = r.Process.Adjacency[dstID].Add(srcPortID)
var (
edgeKeyEgress = srcPortID + report.IDDelim + dstPortID
edgeKeyIngress = dstPortID + report.IDDelim + srcPortID
edgeKeyEgress = report.MakeEdgeID(srcPortID, dstPortID)
edgeKeyIngress = report.MakeEdgeID(dstPortID, srcPortID)
)
r.Process.EdgeMetadatas[edgeKeyEgress] = report.EdgeMetadata{
WithConnCountTCP: true,

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"math/rand"
"net"
"strconv"
"time"
"github.com/weaveworks/scope/report"
@@ -56,14 +57,14 @@ func DemoReport(nodeCount int) report.Report {
src = hosts[rand.Intn(len(hosts))]
dst = hosts[rand.Intn(len(hosts))]
srcPort = rand.Intn(50000) + 10000
srcPortID = fmt.Sprintf("%s%s%s%d", report.ScopeDelim, src, report.ScopeDelim, srcPort)
dstPortID = fmt.Sprintf("%s%s%s%d", report.ScopeDelim, dst, report.ScopeDelim, c.dstPort)
srcID = "hostX" + report.IDDelim + srcPortID
dstID = "hostX" + report.IDDelim + dstPortID
srcAddressID = fmt.Sprintf("%s%s", report.ScopeDelim, src)
dstAddressID = fmt.Sprintf("%s%s", report.ScopeDelim, dst)
nodeSrcAddressID = "hostX" + report.IDDelim + srcAddressID
nodeDstAddressID = "hostX" + report.IDDelim + dstAddressID
srcPortID = report.MakeEndpointNodeID("", src, strconv.Itoa(srcPort))
dstPortID = report.MakeEndpointNodeID("", dst, strconv.Itoa(c.dstPort))
srcID = report.MakeAdjacencyID("hostX", srcPortID)
dstID = report.MakeAdjacencyID("hostX", dstPortID)
srcAddressID = report.MakeAddressNodeID("", src)
dstAddressID = report.MakeAddressNodeID("", dst)
nodeSrcAddressID = report.MakeAdjacencyID("hostX", srcAddressID)
nodeDstAddressID = report.MakeAdjacencyID("hostX", dstAddressID)
)
// Process topology
@@ -84,8 +85,8 @@ func DemoReport(nodeCount int) report.Report {
}
r.Process.Adjacency[dstID] = r.Process.Adjacency[dstID].Add(srcPortID)
var (
edgeKeyEgress = srcPortID + report.IDDelim + dstPortID
edgeKeyIngress = dstPortID + report.IDDelim + srcPortID
edgeKeyEgress = report.MakeEdgeID(srcPortID, dstPortID)
edgeKeyIngress = report.MakeEdgeID(dstPortID, srcPortID)
)
r.Process.EdgeMetadatas[edgeKeyEgress] = report.EdgeMetadata{
WithConnCountTCP: true,

View File

@@ -3,7 +3,6 @@ package main
import (
"fmt"
"log"
"net"
"strconv"
"time"
@@ -44,10 +43,10 @@ func addConnection(
hostID, hostName string,
) {
var (
scopedLocal = scopedIP(hostID, c.LocalAddress)
scopedRemote = scopedIP(hostID, c.RemoteAddress)
key = hostID + report.IDDelim + scopedLocal
edgeKey = scopedLocal + report.IDDelim + scopedRemote
scopedLocal = report.MakeAddressNodeID(hostID, c.LocalAddress.String())
scopedRemote = report.MakeAddressNodeID(hostID, c.RemoteAddress.String())
key = report.MakeAdjacencyID(hostID, scopedLocal)
edgeKey = report.MakeEdgeID(scopedLocal, scopedRemote)
)
r.Network.Adjacency[key] = r.Network.Adjacency[key].Add(scopedRemote)
@@ -66,10 +65,10 @@ func addConnection(
if c.Proc.PID > 0 {
var (
scopedLocal = scopedIPPort(hostID, c.LocalAddress, c.LocalPort)
scopedRemote = scopedIPPort(hostID, c.RemoteAddress, c.RemotePort)
key = hostID + report.IDDelim + scopedLocal
edgeKey = scopedLocal + report.IDDelim + scopedRemote
scopedLocal = report.MakeEndpointNodeID(hostID, c.LocalAddress.String(), strconv.Itoa(int(c.LocalPort)))
scopedRemote = report.MakeEndpointNodeID(hostID, c.RemoteAddress.String(), strconv.Itoa(int(c.RemotePort)))
key = report.MakeAdjacencyID(hostID, scopedLocal)
edgeKey = report.MakeEdgeID(scopedLocal, scopedRemote)
)
r.Process.Adjacency[key] = r.Process.Adjacency[key].Add(scopedRemote)
@@ -91,16 +90,3 @@ func addConnection(
r.Process.EdgeMetadatas[edgeKey] = edgeMeta
}
}
// scopedIP makes an IP unique over multiple networks.
func scopedIP(scope string, ip net.IP) string {
if ip.IsLoopback() {
return scope + report.ScopeDelim + ip.String()
}
return report.ScopeDelim + ip.String()
}
// scopedIPPort makes an IP+port tuple unique over multiple networks.
func scopedIPPort(scope string, ip net.IP, port uint16) string {
return scopedIP(scope, ip) + report.ScopeDelim + strconv.FormatUint(uint64(port), 10)
}

View File

@@ -9,23 +9,6 @@ import (
"github.com/weaveworks/scope/report"
)
func TestScopedIP(t *testing.T) {
const scope = "my-scope"
for ip, want := range map[string]string{
"1.2.3.4": report.ScopeDelim + "1.2.3.4",
"192.168.1.2": report.ScopeDelim + "192.168.1.2",
"127.0.0.1": scope + report.ScopeDelim + "127.0.0.1", // loopback
"::1": scope + report.ScopeDelim + "::1", // loopback
"fd00::451b:b714:85da:489e": report.ScopeDelim + "fd00::451b:b714:85da:489e", // global address
"fe80::82ee:73ff:fe83:588f": report.ScopeDelim + "fe80::82ee:73ff:fe83:588f", // link-local address
} {
if have := scopedIP(scope, net.ParseIP(ip)); have != want {
t.Errorf("%q: have %q, want %q", ip, have, want)
}
}
}
var (
fixLocalAddress = net.ParseIP("192.168.1.1")
fixLocalPort = uint16(80)
@@ -83,8 +66,8 @@ func TestSpyNetwork(t *testing.T) {
procspy.SetFixtures(fixConnections)
const (
nodeID = "heinz-tomato-ketchup"
nodeName = "frenchs-since-1904"
nodeID = "heinz-tomato-ketchup" // TODO rename to hostID
nodeName = "frenchs-since-1904" // TODO rename to hostNmae
)
r := spy(nodeID, nodeName, false)
@@ -97,9 +80,9 @@ func TestSpyNetwork(t *testing.T) {
}
var (
scopedLocal = scopedIP(nodeID, fixLocalAddress)
scopedRemote = scopedIP(nodeID, fixRemoteAddress)
localKey = nodeID + report.IDDelim + scopedLocal
scopedLocal = report.MakeAddressNodeID(nodeID, fixLocalAddress.String())
scopedRemote = report.MakeAddressNodeID(nodeID, fixRemoteAddress.String())
localKey = report.MakeAdjacencyID(nodeID, scopedLocal)
)
if want, have := 1, len(r.Network.Adjacency[localKey]); want != have {
@@ -119,17 +102,17 @@ func TestSpyProcess(t *testing.T) {
procspy.SetFixtures(fixConnectionsWithProcesses)
const (
nodeID = "nikon"
nodeName = "fishermans-friend"
nodeID = "nikon" // TODO rename to hostID
nodeName = "fishermans-friend" // TODO rename to hostNmae
)
r := spy(nodeID, nodeName, true)
// buf, _ := json.MarshalIndent(r, "", " ") ; t.Logf("\n%s\n", buf)
var (
scopedLocal = scopedIPPort(nodeID, fixLocalAddress, fixLocalPort)
scopedRemote = scopedIPPort(nodeID, fixRemoteAddress, fixRemotePort)
localKey = nodeID + report.IDDelim + scopedLocal
scopedLocal = report.MakeEndpointNodeID(nodeID, fixLocalAddress.String(), strconv.Itoa(int(fixLocalPort)))
scopedRemote = report.MakeEndpointNodeID(nodeID, fixRemoteAddress.String(), strconv.Itoa(int(fixRemotePort)))
localKey = report.MakeAdjacencyID(nodeID, scopedLocal)
)
if want, have := 1, len(r.Process.Adjacency[localKey]); want != have {

29
report/fixture_test.go Normal file
View File

@@ -0,0 +1,29 @@
package report_test
import (
"github.com/weaveworks/scope/report"
)
var (
clientHostID = "client.host.com"
clientHostName = clientHostID
clientHostNodeID = report.MakeHostNodeID(clientHostID)
clientAddress = "10.10.10.20"
serverHostID = "server.host.com"
serverHostName = serverHostID
serverHostNodeID = report.MakeHostNodeID(serverHostID)
serverAddress = "10.10.10.1"
unknownHostID = "" // by definition, we don't know it
unknownAddress = "172.16.93.112" // will be a pseudonode, no corresponding host
client54001EndpointNodeID = report.MakeEndpointNodeID(clientHostID, clientAddress, "54001") // i.e. curl
client54002EndpointNodeID = report.MakeEndpointNodeID(clientHostID, clientAddress, "54002") // also curl
server80EndpointNodeID = report.MakeEndpointNodeID(serverHostID, serverAddress, "80") // i.e. apache
unknown1EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10001")
unknown2EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10002")
unknown3EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10003")
clientAddressNodeID = report.MakeAddressNodeID(clientHostID, clientAddress)
serverAddressNodeID = report.MakeAddressNodeID(serverHostID, serverAddress)
unknownAddressNodeID = report.MakeAddressNodeID(unknownHostID, unknownAddress)
)

View File

@@ -1,29 +1,117 @@
package report
import (
"fmt"
"net"
"strings"
)
// IDAddresser is used to get the IP address from an addressID. Or nil.
// TheInternet is used as a node ID to indicate a remote IP.
const TheInternet = "theinternet"
// Delimiters are used to separate parts of node IDs, to guarantee uniqueness
// in particular contexts.
const (
// ScopeDelim is a general-purpose delimiter used within node IDs to
// separate different contextual scopes. Different topologies have
// different key structures.
ScopeDelim = ";"
// EdgeDelim separates two node IDs when they need to exist in the same key.
// Concretely, it separates node IDs in keys that represent edges.
EdgeDelim = "|"
)
// MakeAdjacencyID produces an adjacency ID from composite parts.
func MakeAdjacencyID(hostID, srcNodeID string) string {
// Here we rely on the fact that every possible source node ID has the
// host ID as the first scope-delimited field, and therefore don't
// duplicate that information.
return ">" + srcNodeID
}
// ParseAdjacencyID splits an adjacency ID to its composite parts.
func ParseAdjacencyID(adjacencyID string) (hostID, srcNodeID string, ok bool) {
if !strings.HasPrefix(adjacencyID, ">") {
return "", "", false
}
// This relies on every node ID having hostID as its first scoped field.
adjacencyID = adjacencyID[1:]
fields := strings.SplitN(adjacencyID, ScopeDelim, 2)
if len(fields) != 2 {
return "", "", false
}
return fields[0], adjacencyID, true
}
// MakeEdgeID produces an edge ID from composite parts.
func MakeEdgeID(srcNodeID, dstNodeID string) string {
return srcNodeID + EdgeDelim + dstNodeID
}
// ParseEdgeID splits an edge ID to its composite parts.
func ParseEdgeID(edgeID string) (srcNodeID, dstNodeID string, ok bool) {
fields := strings.SplitN(edgeID, EdgeDelim, 2)
if len(fields) != 2 {
return "", "", false
}
return fields[0], fields[1], true
}
// MakeEndpointNodeID produces an endpoint node ID from its composite parts.
func MakeEndpointNodeID(hostID, address, port string) string {
return MakeAddressNodeID(hostID, address) + ScopeDelim + port
}
// MakeAddressNodeID produces an address node ID from its composite parts.
func MakeAddressNodeID(hostID, address string) string {
return hostID + ScopeDelim + address
}
// MakeProcessNodeID produces a process node ID from its composite parts.
func MakeProcessNodeID(hostID, pid string) string {
return hostID + ScopeDelim + pid
}
// MakeHostNodeID produces a host node ID from its composite parts.
func MakeHostNodeID(hostID string) string {
// hostIDs come from the probe and are presumed to be globally-unique.
// But, suffix something to elicit failures if we try to use probe host
// IDs directly as node IDs in the host topology.
return hostID + ScopeDelim + "<host>"
}
// MakePseudoNodeID produces a pseudo node ID from its composite parts.
func MakePseudoNodeID(parts ...string) string {
return strings.Join(append([]string{"pseudo"}, parts...), ScopeDelim)
}
// IDAddresser tries to convert a node ID to a net.IP, if possible.
type IDAddresser func(string) net.IP
// AddressIPPort translates "scope;ip;port" to the IP address. These are used
// by Process topologies.
func AddressIPPort(id string) net.IP {
parts := strings.SplitN(id, ScopeDelim, 3)
if len(parts) != 3 {
return nil // hmm
// EndpointIDAddresser converts an endpoint node ID to an IP.
func EndpointIDAddresser(id string) net.IP {
fields := strings.SplitN(id, ScopeDelim, 3)
if len(fields) != 3 {
//log.Printf("EndpointIDAddresser: bad input %q", id)
return nil
}
return net.ParseIP(parts[1])
return net.ParseIP(fields[1])
}
// AddressIP translates "scope;ip" to the IP address. These are used by
// Network topologies.
func AddressIP(id string) net.IP {
parts := strings.SplitN(id, ScopeDelim, 2)
if len(parts) != 2 {
return nil // hmm
// AddressIDAddresser converts an address node ID to an IP.
func AddressIDAddresser(id string) net.IP {
fields := strings.SplitN(id, ScopeDelim, 2)
if len(fields) != 2 {
//log.Printf("AddressIDAddresser: bad input %q", id)
return nil
}
return net.ParseIP(parts[1])
return net.ParseIP(fields[1])
}
// PanicIDAddresser will panic if it's ever called. It's used in topologies
// where there are never any edges, and so it's nonsensical to try and extract
// IPs from the node IDs.
func PanicIDAddresser(id string) net.IP {
panic(fmt.Sprintf("PanicIDAddresser called on %q", id))
}

147
report/id_test.go Normal file
View File

@@ -0,0 +1,147 @@
package report_test
import (
"net"
"reflect"
"testing"
"github.com/weaveworks/scope/report"
)
func TestAdjacencyID(t *testing.T) {
for _, bad := range []string{
client54001EndpointNodeID,
client54002EndpointNodeID,
unknown1EndpointNodeID,
unknown2EndpointNodeID,
unknown3EndpointNodeID,
clientAddressNodeID,
serverAddressNodeID,
unknownAddressNodeID,
clientHostNodeID,
serverHostNodeID,
">1.2.3.4",
">",
";",
"",
} {
if hostID, srcNodeID, ok := report.ParseAdjacencyID(bad); ok {
t.Errorf("%q: expected failure, but got (%q, %q)", bad, hostID, srcNodeID)
}
}
for input, want := range map[string]struct{ hostID, srcNodeID string }{
report.MakeAdjacencyID("a", report.MakeEndpointNodeID("a", "b", "c")): {"a", report.MakeEndpointNodeID("a", "b", "c")},
report.MakeAdjacencyID("a", report.MakeAddressNodeID("a", "b")): {"a", report.MakeAddressNodeID("a", "b")},
report.MakeAdjacencyID("a", report.MakeProcessNodeID("a", "b")): {"a", report.MakeProcessNodeID("a", "b")},
report.MakeAdjacencyID("a", report.MakeHostNodeID("a")): {"a", report.MakeHostNodeID("a")},
">host.com;1.2.3.4": {"host.com", "host.com;1.2.3.4"},
">a;b;c": {"a", "a;b;c"},
">a;b": {"a", "a;b"},
">a;": {"a", "a;"},
">;b": {"", ";b"},
">;": {"", ";"},
} {
hostID, srcNodeID, ok := report.ParseAdjacencyID(input)
if !ok {
t.Errorf("%q: not OK", input)
continue
}
if want, have := want.hostID, hostID; want != have {
t.Errorf("%q: want %q, have %q", input, want, have)
}
if want, have := want.srcNodeID, srcNodeID; want != have {
t.Errorf("%q: want %q, have %q", input, want, have)
}
}
}
func TestEdgeID(t *testing.T) {
for _, bad := range []string{
client54001EndpointNodeID,
client54002EndpointNodeID,
unknown1EndpointNodeID,
unknown2EndpointNodeID,
unknown3EndpointNodeID,
clientAddressNodeID,
serverAddressNodeID,
unknownAddressNodeID,
clientHostNodeID,
serverHostNodeID,
">1.2.3.4",
">",
";",
"",
} {
if srcNodeID, dstNodeID, ok := report.ParseEdgeID(bad); ok {
t.Errorf("%q: expected failure, but got (%q, %q)", bad, srcNodeID, dstNodeID)
}
}
for input, want := range map[string]struct{ srcNodeID, dstNodeID string }{
report.MakeEdgeID("a", report.MakeEndpointNodeID("a", "b", "c")): {"a", report.MakeEndpointNodeID("a", "b", "c")},
report.MakeEdgeID("a", report.MakeAddressNodeID("a", "b")): {"a", report.MakeAddressNodeID("a", "b")},
report.MakeEdgeID("a", report.MakeProcessNodeID("a", "b")): {"a", report.MakeProcessNodeID("a", "b")},
report.MakeEdgeID("a", report.MakeHostNodeID("a")): {"a", report.MakeHostNodeID("a")},
"host.com|1.2.3.4": {"host.com", "1.2.3.4"},
"a|b;c": {"a", "b;c"},
"a|b": {"a", "b"},
"a|": {"a", ""},
"|b": {"", "b"},
"|": {"", ""},
} {
srcNodeID, dstNodeID, ok := report.ParseEdgeID(input)
if !ok {
t.Errorf("%q: not OK", input)
continue
}
if want, have := want.srcNodeID, srcNodeID; want != have {
t.Errorf("%q: want %q, have %q", input, want, have)
}
if want, have := want.dstNodeID, dstNodeID; want != have {
t.Errorf("%q: want %q, have %q", input, want, have)
}
}
}
func TestEndpointIDAddresser(t *testing.T) {
if nodeID := "1.2.4.5"; report.EndpointIDAddresser(nodeID) != nil {
t.Errorf("%q: bad node ID parsed as good", nodeID)
}
var (
nodeID = report.MakeEndpointNodeID(clientHostID, clientAddress, "12345")
want = net.ParseIP(clientAddress)
have = report.EndpointIDAddresser(nodeID)
)
if !reflect.DeepEqual(want, have) {
t.Errorf("want %s, have %s", want, have)
}
}
func TestAddressIDAddresser(t *testing.T) {
if nodeID := "1.2.4.5"; report.AddressIDAddresser(nodeID) != nil {
t.Errorf("%q: bad node ID parsed as good", nodeID)
}
var (
nodeID = report.MakeAddressNodeID(clientHostID, clientAddress)
want = net.ParseIP(clientAddress)
have = report.AddressIDAddresser(nodeID)
)
if !reflect.DeepEqual(want, have) {
t.Errorf("want %s, have %s", want, have)
}
}
func TestPanicIDAddresser(t *testing.T) {
if panicked := func() (recovered bool) {
defer func() {
if r := recover(); r != nil {
recovered = true
}
}()
report.PanicIDAddresser("irrelevant")
return false
}(); !panicked {
t.Errorf("expected panic, didn't get it")
}
}

View File

@@ -151,7 +151,7 @@ func GenericPseudoNode(src string, srcMapped RenderableNode, dst string) (Mapped
srcNodeAddr, srcNodePort := trySplitAddr(src)
dstNodeAddr, _ := trySplitAddr(dst)
outputID = strings.Join([]string{"pseudo:", dstNodeAddr, srcNodeAddr, srcNodePort}, ScopeDelim)
outputID = MakePseudoNodeID(dstNodeAddr, srcNodeAddr, srcNodePort)
maj, min = dstNodeAddr, ""
}
@@ -174,7 +174,7 @@ func GenericGroupedPseudoNode(src string, srcMapped RenderableNode, dst string)
// When grouping, emit one pseudo node per (srcNodeAddress, dstNodeAddr)
dstNodeAddr, _ := trySplitAddr(dst)
outputID = strings.Join([]string{"pseudo:", dstNodeAddr, srcMapped.ID}, ScopeDelim)
outputID = MakePseudoNodeID(dstNodeAddr, srcMapped.ID)
maj, min = dstNodeAddr, ""
}
@@ -193,6 +193,12 @@ func InternetOnlyPseudoNode(_ string, _ RenderableNode, dst string) (MappedNode,
return MappedNode{}, false
}
// trySplitAddr is basically ParseArbitraryNodeID, since its callsites
// (pseudo funcs) just have opaque node IDs and don't know what topology they
// come from. Without changing how pseudo funcs work, we can't make it much
// smarter.
//
// TODO change how pseudofuncs work, and eliminate this helper.
func trySplitAddr(addr string) (string, string) {
fields := strings.SplitN(addr, ScopeDelim, 3)
if len(fields) == 3 {

View File

@@ -15,7 +15,7 @@ func TestUngroupedMapping(t *testing.T) {
}{
{
f: NetworkHostname,
id: ScopeDelim + "1.2.3.4",
id: MakeAddressNodeID("", "1.2.3.4"),
meta: NodeMetadata{
"name": "my.host",
},
@@ -27,7 +27,7 @@ func TestUngroupedMapping(t *testing.T) {
},
{
f: NetworkHostname,
id: ScopeDelim + "1.2.3.4",
id: MakeAddressNodeID("", "1.2.3.4"),
meta: NodeMetadata{
"name": "localhost",
},

View File

@@ -93,8 +93,8 @@ func MakeReport() Report {
func (r Report) SquashRemote() Report {
localNets := r.HostMetadatas.LocalNets()
return Report{
Process: Squash(r.Process, AddressIPPort, localNets),
Network: Squash(r.Network, AddressIP, localNets),
Process: Squash(r.Process, EndpointIDAddresser, localNets),
Network: Squash(r.Network, AddressIDAddresser, localNets),
HostMetadatas: r.HostMetadatas,
}
}

View File

@@ -1,14 +1,8 @@
package report
import (
"log"
"net"
"strings"
)
const (
// TheInternet is the ID that we assign to the super-node composed of all
// remote nodes that have been squashed together.
TheInternet = "theinternet"
)
// Squash takes a Topology, and folds all remote nodes into a supernode.
@@ -34,9 +28,13 @@ func Squash(t Topology, f IDAddresser, localNets []*net.IPNet) Topology {
// Edge metadata keys are "<src node ID>|<dst node ID>". If the dst node
// ID is remote, rename it to TheInternet.
for key, metadata := range t.EdgeMetadatas {
parts := strings.SplitN(key, IDDelim, 2)
if ip := f(parts[1]); ip != nil && isRemote(ip) {
key = parts[0] + IDDelim + TheInternet
srcNodeID, dstNodeID, ok := ParseEdgeID(key)
if !ok {
log.Printf("bad edge ID %q", key)
continue
}
if ip := f(dstNodeID); ip != nil && isRemote(ip) {
key = MakeEdgeID(srcNodeID, TheInternet)
}
// Could be we're merging two keys into one now.

View File

@@ -166,7 +166,7 @@ func TestSquashTopology(t *testing.T) {
NodeMetadatas: reportToSquash().Process.NodeMetadatas,
}
have := Squash(reportToSquash().Process, AddressIPPort, reportToSquash().HostMetadatas.LocalNets())
have := Squash(reportToSquash().Process, EndpointIDAddresser, reportToSquash().HostMetadatas.LocalNets())
if !reflect.DeepEqual(want, have) {
t.Errorf("want\n\t%#v, have\n\t%#v", want, have)
}

View File

@@ -1,20 +1,11 @@
package report
import (
"log"
"reflect"
"strings"
)
const (
// ScopeDelim separates the scope portion of an address from the address
// string itself.
ScopeDelim = ";"
// IDDelim separates fields in a node ID.
IDDelim = "|"
localUnknown = "localUnknown"
)
const localUnknown = "localUnknown"
// Topology describes a specific view of a network. It consists of nodes and
// edges, represented by Adjacency, and metadata about those nodes and edges,
@@ -122,12 +113,14 @@ func (t Topology) RenderBy(mapFunc MapFunc, pseudoFunc PseudoFunc) map[string]Re
// Walk the graph and make connections.
for src, dsts := range t.Adjacency {
var (
fields = strings.SplitN(src, IDDelim, 2) // "<host>|<address>"
srcOriginHostID = fields[0]
srcNodeAddress = fields[1]
srcRenderableID = address2mapped[srcNodeAddress] // must exist
srcRenderableNode = nodes[srcRenderableID] // must exist
srcOriginHostID, srcNodeAddress, ok = ParseAdjacencyID(src)
srcRenderableID = address2mapped[srcNodeAddress] // must exist
srcRenderableNode = nodes[srcRenderableID] // must exist
)
if !ok {
log.Printf("bad adjacency ID %q", src)
continue
}
for _, dstNodeAddress := range dsts {
dstRenderableID, ok := address2mapped[dstNodeAddress]
@@ -150,7 +143,7 @@ func (t Topology) RenderBy(mapFunc MapFunc, pseudoFunc PseudoFunc) map[string]Re
srcRenderableNode.Adjacency = srcRenderableNode.Adjacency.Add(dstRenderableID)
srcRenderableNode.OriginHosts = srcRenderableNode.OriginHosts.Add(srcOriginHostID)
srcRenderableNode.OriginNodes = srcRenderableNode.OriginNodes.Add(srcNodeAddress)
edgeID := srcNodeAddress + IDDelim + dstNodeAddress
edgeID := MakeEdgeID(srcNodeAddress, dstNodeAddress)
if md, ok := t.EdgeMetadatas[edgeID]; ok {
srcRenderableNode.Metadata.Merge(md.Transform())
}
@@ -169,13 +162,15 @@ func (t Topology) RenderBy(mapFunc MapFunc, pseudoFunc PseudoFunc) map[string]Re
func (t Topology) EdgeMetadata(mapFunc MapFunc, srcRenderableID, dstRenderableID string) EdgeMetadata {
metadata := EdgeMetadata{}
for edgeID, edgeMeta := range t.EdgeMetadatas {
edgeParts := strings.SplitN(edgeID, IDDelim, 2)
src := edgeParts[0]
src, dst, ok := ParseEdgeID(edgeID)
if !ok {
log.Printf("bad edge ID %q", edgeID)
continue
}
if src != TheInternet {
mapped, _ := mapFunc(src, t.NodeMetadatas[src])
src = mapped.ID
}
dst := edgeParts[1]
if dst != TheInternet {
mapped, _ := mapFunc(dst, t.NodeMetadatas[dst])
dst = mapped.ID

View File

@@ -13,27 +13,32 @@ func init() {
spew.Config.SortKeys = true // :\
}
const (
client54001 = ScopeDelim + "10.10.10.20" + ScopeDelim + "54001" // curl (1)
client54002 = ScopeDelim + "10.10.10.20" + ScopeDelim + "54002" // curl (2)
unknownClient1 = ScopeDelim + "10.10.10.10" + ScopeDelim + "54010" // we want to ensure two unknown clients, connnected
unknownClient2 = ScopeDelim + "10.10.10.10" + ScopeDelim + "54020" // to the same server, are deduped.
unknownClient3 = ScopeDelim + "10.10.10.11" + ScopeDelim + "54020" // Check this one isn't deduped
server80 = ScopeDelim + "192.168.1.1" + ScopeDelim + "80" // apache
var (
clientHostID = "client.hostname.com"
serverHostID = "server.hostname.com"
randomHostID = "random.hostname.com"
unknownHostID = ""
clientIP = ScopeDelim + "10.10.10.20"
serverIP = ScopeDelim + "192.168.1.1"
randomIP = ScopeDelim + "172.16.11.9" // only in Network topology
unknownIP = ScopeDelim + "10.10.10.10"
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 Network topology
unknownIP = MakeAddressNodeID(unknownHostID, "10.10.10.10")
)
var (
report = Report{
Process: Topology{
Adjacency: Adjacency{
"client.hostname.com" + IDDelim + client54001: NewIDList(server80),
"client.hostname.com" + IDDelim + client54002: NewIDList(server80),
"server.hostname.com" + IDDelim + server80: NewIDList(client54001, client54002, unknownClient1, unknownClient2, unknownClient3),
MakeAdjacencyID("client.hostname.com", client54001): NewIDList(server80),
MakeAdjacencyID("client.hostname.com", client54002): NewIDList(server80),
MakeAdjacencyID("server.hostname.com", server80): NewIDList(client54001, client54002, unknownClient1, unknownClient2, unknownClient3),
},
NodeMetadatas: NodeMetadatas{
// NodeMetadata is arbitrary. We're free to put only precisely what we
@@ -56,38 +61,38 @@ var (
},
},
EdgeMetadatas: EdgeMetadatas{
client54001 + IDDelim + server80: EdgeMetadata{
MakeEdgeID(client54001, server80): EdgeMetadata{
WithBytes: true,
BytesIngress: 100,
BytesEgress: 10,
},
client54002 + IDDelim + server80: EdgeMetadata{
MakeEdgeID(client54002, server80): EdgeMetadata{
WithBytes: true,
BytesIngress: 200,
BytesEgress: 20,
},
server80 + IDDelim + client54001: EdgeMetadata{
MakeEdgeID(server80, client54001): EdgeMetadata{
WithBytes: true,
BytesIngress: 10,
BytesEgress: 100,
},
server80 + IDDelim + client54002: EdgeMetadata{
MakeEdgeID(server80, client54002): EdgeMetadata{
WithBytes: true,
BytesIngress: 20,
BytesEgress: 200,
},
server80 + IDDelim + unknownClient1: EdgeMetadata{
MakeEdgeID(server80, unknownClient1): EdgeMetadata{
WithBytes: true,
BytesIngress: 30,
BytesEgress: 300,
},
server80 + IDDelim + unknownClient2: EdgeMetadata{
MakeEdgeID(server80, unknownClient2): EdgeMetadata{
WithBytes: true,
BytesIngress: 40,
BytesEgress: 400,
},
server80 + IDDelim + unknownClient3: EdgeMetadata{
MakeEdgeID(server80, unknownClient3): EdgeMetadata{
WithBytes: true,
BytesIngress: 50,
BytesEgress: 500,
@@ -96,9 +101,9 @@ var (
},
Network: Topology{
Adjacency: Adjacency{
"client.hostname.com" + IDDelim + clientIP: NewIDList(serverIP),
"random.hostname.com" + IDDelim + randomIP: NewIDList(serverIP),
"server.hostname.com" + IDDelim + serverIP: NewIDList(clientIP, unknownIP), // no backlink to random
MakeAdjacencyID("client.hostname.com", clientIP): NewIDList(serverIP),
MakeAdjacencyID("random.hostname.com", randomIP): NewIDList(serverIP),
MakeAdjacencyID("server.hostname.com", serverIP): NewIDList(clientIP, unknownIP), // no backlink to random
},
NodeMetadatas: NodeMetadatas{
clientIP: NodeMetadata{
@@ -112,19 +117,19 @@ var (
},
},
EdgeMetadatas: EdgeMetadatas{
clientIP + IDDelim + serverIP: EdgeMetadata{
MakeEdgeID(clientIP, serverIP): EdgeMetadata{
WithConnCountTCP: true,
MaxConnCountTCP: 3,
},
randomIP + IDDelim + serverIP: EdgeMetadata{
MakeEdgeID(randomIP, serverIP): EdgeMetadata{
WithConnCountTCP: true,
MaxConnCountTCP: 20, // dangling connections, weird but possible
},
serverIP + IDDelim + clientIP: EdgeMetadata{
MakeEdgeID(serverIP, clientIP): EdgeMetadata{
WithConnCountTCP: true,
MaxConnCountTCP: 3,
},
serverIP + IDDelim + unknownIP: EdgeMetadata{
MakeEdgeID(serverIP, unknownIP): EdgeMetadata{
WithConnCountTCP: true,
MaxConnCountTCP: 7,
},
@@ -143,7 +148,7 @@ func TestRenderByProcessPID(t *testing.T) {
Pseudo: false,
Adjacency: NewIDList("pid:server-80-domain:215"),
OriginHosts: NewIDList("client.hostname.com"),
OriginNodes: NewIDList(";10.10.10.20;54001"),
OriginNodes: NewIDList("client.hostname.com;10.10.10.20;54001"),
Metadata: AggregateMetadata{
KeyBytesIngress: 100,
KeyBytesEgress: 10,
@@ -157,7 +162,7 @@ func TestRenderByProcessPID(t *testing.T) {
Pseudo: false,
Adjacency: NewIDList("pid:server-80-domain:215"),
OriginHosts: NewIDList("client.hostname.com"),
OriginNodes: NewIDList(";10.10.10.20;54002"),
OriginNodes: NewIDList("client.hostname.com;10.10.10.20;54002"),
Metadata: AggregateMetadata{
KeyBytesIngress: 200,
KeyBytesEgress: 20,
@@ -172,24 +177,24 @@ func TestRenderByProcessPID(t *testing.T) {
Adjacency: NewIDList(
"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",
"pseudo;10.10.10.10;192.168.1.1;80",
"pseudo;10.10.10.11;192.168.1.1;80",
),
OriginHosts: NewIDList("server.hostname.com"),
OriginNodes: NewIDList(";192.168.1.1;80"),
OriginNodes: NewIDList("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",
"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",
"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{},
@@ -214,7 +219,7 @@ func TestRenderByProcessPIDGrouped(t *testing.T) {
Pseudo: false,
Adjacency: NewIDList("apache"),
OriginHosts: NewIDList("client.hostname.com"),
OriginNodes: NewIDList(";10.10.10.20;54001", ";10.10.10.20;54002"),
OriginNodes: NewIDList("client.hostname.com;10.10.10.20;54001", "client.hostname.com;10.10.10.20;54002"),
Metadata: AggregateMetadata{
KeyBytesIngress: 300,
KeyBytesEgress: 30,
@@ -228,24 +233,24 @@ func TestRenderByProcessPIDGrouped(t *testing.T) {
Pseudo: false,
Adjacency: NewIDList(
"curl",
"pseudo:;10.10.10.10;apache",
"pseudo:;10.10.10.11;apache",
"pseudo;10.10.10.10;apache",
"pseudo;10.10.10.11;apache",
),
OriginHosts: NewIDList("server.hostname.com"),
OriginNodes: NewIDList(";192.168.1.1;80"),
OriginNodes: NewIDList("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",
"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",
"pseudo;10.10.10.11;apache": {
ID: "pseudo;10.10.10.11;apache",
LabelMajor: "10.10.10.11",
Pseudo: true,
Metadata: AggregateMetadata{},
@@ -267,7 +272,7 @@ func TestRenderByNetworkHostname(t *testing.T) {
Pseudo: false,
Adjacency: NewIDList("host:server.hostname.com"),
OriginHosts: NewIDList("client.hostname.com"),
OriginNodes: NewIDList(";10.10.10.20"),
OriginNodes: NewIDList("client.hostname.com;10.10.10.20"),
Metadata: AggregateMetadata{
KeyMaxConnCountTCP: 3,
},
@@ -280,7 +285,7 @@ func TestRenderByNetworkHostname(t *testing.T) {
Pseudo: false,
Adjacency: NewIDList("host:server.hostname.com"),
OriginHosts: NewIDList("random.hostname.com"),
OriginNodes: NewIDList(";172.16.11.9"),
OriginNodes: NewIDList("random.hostname.com;172.16.11.9"),
Metadata: AggregateMetadata{
KeyMaxConnCountTCP: 20,
},
@@ -291,15 +296,15 @@ func TestRenderByNetworkHostname(t *testing.T) {
LabelMinor: "hostname.com", // after first .
Rank: "server",
Pseudo: false,
Adjacency: NewIDList("host:client.hostname.com", "pseudo:;10.10.10.10;192.168.1.1;"),
Adjacency: NewIDList("host:client.hostname.com", "pseudo;10.10.10.10;192.168.1.1;"),
OriginHosts: NewIDList("server.hostname.com"),
OriginNodes: NewIDList(";192.168.1.1"),
OriginNodes: NewIDList("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;",
"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: "",