Make NodeMetadatas immutable.

This commit is contained in:
Tom Wilkie
2016-01-23 13:02:16 -08:00
parent 6b56475766
commit bc3f946810
7 changed files with 214 additions and 73 deletions

View File

@@ -27,14 +27,13 @@ func MakeRenderableNodes(t report.Topology) RenderableNodes {
// Push EdgeMetadata to both ends of the edges
for srcID, srcNode := range result {
for dstID, emd := range srcNode.Edges {
srcNode.Edges.ForEach(func(dstID string, emd report.EdgeMetadata) {
srcNode.EdgeMetadata = srcNode.EdgeMetadata.Flatten(emd)
dstNode := result[dstID]
dstNode.EdgeMetadata = dstNode.EdgeMetadata.Flatten(emd.Reversed())
result[dstID] = dstNode
}
})
result[srcID] = srcNode
}
return result

View File

@@ -1,38 +1,176 @@
package report
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
"reflect"
"sort"
"github.com/mndrix/ps"
)
// EdgeMetadatas collect metadata about each edge in a topology. Keys are the
// remote node IDs, as in Adjacency.
type EdgeMetadatas map[string]EdgeMetadata
// Copy returns a value copy of the EdgeMetadatas.
func (e EdgeMetadatas) Copy() EdgeMetadatas {
cp := make(EdgeMetadatas, len(e))
for k, v := range e {
cp[k] = v.Copy()
}
return cp
type EdgeMetadatas struct {
psMap ps.Map
}
// Merge merges the other object into this one, and returns the result object.
// The original is not modified.
func (e EdgeMetadatas) Merge(other EdgeMetadatas) EdgeMetadatas {
cp := e.Copy()
for k, v := range other {
cp[k] = cp[k].Merge(v)
// EmptyEdgeMetadatas is the set of empty EdgeMetadatas.
var EmptyEdgeMetadatas = EdgeMetadatas{ps.NewMap()}
// MakeEdgeMetadatas returns EmptyEdgeMetadatas
func MakeEdgeMetadatas() EdgeMetadatas {
return EmptyEdgeMetadatas
}
// Copy is a noop
func (c EdgeMetadatas) Copy() EdgeMetadatas {
return c
}
// Add value to the counter 'key'
func (c EdgeMetadatas) Add(key string, value EdgeMetadata) EdgeMetadatas {
if existingValue, ok := c.psMap.Lookup(key); ok {
value.Merge(existingValue.(EdgeMetadata))
}
return cp
return EdgeMetadatas{
c.psMap.Set(key, value),
}
}
// Lookup the counter 'key'
func (c EdgeMetadatas) Lookup(key string) (EdgeMetadata, bool) {
existingValue, ok := c.psMap.Lookup(key)
if ok {
return existingValue.(EdgeMetadata), true
}
return EdgeMetadata{}, false
}
// Merge produces a fresh Counters, container the keys from both inputs. When
// both inputs container the same key, the latter value is used.
func (c EdgeMetadatas) Merge(other EdgeMetadatas) EdgeMetadatas {
output := c.psMap
other.psMap.ForEach(func(key string, otherVal interface{}) {
if val, ok := output.Lookup(key); ok {
output = output.Set(key, otherVal.(EdgeMetadata).Merge(val.(EdgeMetadata)))
} else {
output = output.Set(key, otherVal)
}
})
return EdgeMetadatas{output}
}
// Flatten flattens all the EdgeMetadatas in this set and returns the result.
// The original is not modified.
func (e EdgeMetadatas) Flatten() EdgeMetadata {
func (c EdgeMetadatas) Flatten() EdgeMetadata {
result := EdgeMetadata{}
for _, v := range e {
result = result.Flatten(v)
}
c.psMap.ForEach(func(_ string, v interface{}) {
result = result.Flatten(v.(EdgeMetadata))
})
return result
}
// ForEach executes f on each key value pair in the map
func (c EdgeMetadatas) ForEach(fn func(k string, v EdgeMetadata)) {
c.psMap.ForEach(func(key string, value interface{}) {
fn(key, value.(EdgeMetadata))
})
}
func (c EdgeMetadatas) String() string {
keys := []string{}
for _, k := range c.psMap.Keys() {
keys = append(keys, k)
}
sort.Strings(keys)
buf := bytes.NewBufferString("{")
for _, key := range keys {
val, _ := c.psMap.Lookup(key)
fmt.Fprintf(buf, "%s: %v, ", key, val)
}
fmt.Fprintf(buf, "}\n")
return buf.String()
}
// DeepEqual tests equality with other Counters
func (c EdgeMetadatas) DeepEqual(i interface{}) bool {
d, ok := i.(EdgeMetadatas)
if !ok {
return false
}
if c.psMap.Size() != d.psMap.Size() {
return false
}
equal := true
c.psMap.ForEach(func(k string, val interface{}) {
if otherValue, ok := d.psMap.Lookup(k); !ok {
equal = false
} else {
equal = equal && reflect.DeepEqual(val, otherValue)
}
})
return equal
}
func (c EdgeMetadatas) toIntermediate() map[string]EdgeMetadata {
intermediate := map[string]EdgeMetadata{}
c.psMap.ForEach(func(key string, val interface{}) {
intermediate[key] = val.(EdgeMetadata)
})
return intermediate
}
func (c EdgeMetadatas) fromIntermediate(in map[string]EdgeMetadata) EdgeMetadatas {
out := ps.NewMap()
for k, v := range in {
out = out.Set(k, v)
}
return EdgeMetadatas{out}
}
// MarshalJSON implements json.Marshaller
func (c EdgeMetadatas) MarshalJSON() ([]byte, error) {
if c.psMap != nil {
return json.Marshal(c.toIntermediate())
}
return json.Marshal(nil)
}
// UnmarshalJSON implements json.Unmarshaler
func (c *EdgeMetadatas) UnmarshalJSON(input []byte) error {
in := map[string]EdgeMetadata{}
if err := json.Unmarshal(input, &in); err != nil {
return err
}
*c = EdgeMetadatas{}.fromIntermediate(in)
return nil
}
// GobEncode implements gob.Marshaller
func (c EdgeMetadatas) GobEncode() ([]byte, error) {
buf := bytes.Buffer{}
err := gob.NewEncoder(&buf).Encode(c.toIntermediate())
return buf.Bytes(), err
}
// GobDecode implements gob.Unmarshaller
func (c *EdgeMetadatas) GobDecode(input []byte) error {
in := map[string]EdgeMetadata{}
if err := gob.NewDecoder(bytes.NewBuffer(input)).Decode(&in); err != nil {
return err
}
*c = EdgeMetadatas{}.fromIntermediate(in)
return nil
}
// EdgeMetadata describes a superset of the metadata that probes can possibly
// collect about a directed edge between two nodes in any topology.
type EdgeMetadata struct {

View File

@@ -5,6 +5,9 @@ import "sort"
// IDList is a list of string IDs, which are always sorted and unique.
type IDList StringSet
// EmptyIDList is an Empty ID List.
var EmptyIDList = IDList(EmptyStringSet)
// MakeIDList makes a new IDList.
func MakeIDList(ids ...string) IDList {
return IDList(MakeStringSet(ids...))

View File

@@ -21,87 +21,88 @@ func TestMergeEdgeMetadatas(t *testing.T) {
a, b, want report.EdgeMetadatas
}{
"Empty a": {
a: report.EdgeMetadatas{},
b: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
a: report.EmptyEdgeMetadatas,
b: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
},
},
want: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
}),
want: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(1),
MaxConnCountTCP: newu64(2),
},
},
}),
},
"Empty b": {
a: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
a: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(999),
},
},
b: report.EdgeMetadatas{},
want: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
}),
b: report.EmptyEdgeMetadatas,
want: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(999),
},
},
}),
},
"Host merge": {
a: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
a: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(500),
MaxConnCountTCP: newu64(4),
},
},
b: report.EdgeMetadatas{
"hostQ|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
}),
b: report.EmptyEdgeMetadatas.
Add("hostQ|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(1),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(6),
},
},
want: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
}),
want: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(500),
MaxConnCountTCP: newu64(4),
},
"hostQ|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
}).
Add("hostQ|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(1),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(6),
},
},
}),
},
"Edge merge": {
a: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
a: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(12),
EgressByteCount: newu64(1000),
MaxConnCountTCP: newu64(7),
},
},
b: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
}),
b: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(1),
IngressByteCount: newu64(123),
EgressByteCount: newu64(2),
MaxConnCountTCP: newu64(9),
},
},
want: report.EdgeMetadatas{
"hostA|:192.168.1.1:12345|:192.168.1.2:80": report.EdgeMetadata{
}),
want: report.EmptyEdgeMetadatas.
Add("hostA|:192.168.1.1:12345|:192.168.1.2:80",
report.EdgeMetadata{
EgressPacketCount: newu64(13),
IngressByteCount: newu64(123),
EgressByteCount: newu64(1002),
MaxConnCountTCP: newu64(9),
},
},
}),
},
} {
if have := c.a.Merge(c.b); !reflect.DeepEqual(c.want, have) {

View File

@@ -27,8 +27,8 @@ func MakeNode() Node {
return Node{
Counters: EmptyCounters,
Sets: EmptySets,
Adjacency: MakeIDList(),
Edges: EdgeMetadatas{},
Adjacency: EmptyIDList,
Edges: EmptyEdgeMetadatas,
Controls: MakeNodeControls(),
Latest: EmptyLatestMap,
Metrics: Metrics{},
@@ -134,7 +134,7 @@ func (n Node) WithAdjacent(a ...string) Node {
func (n Node) WithEdge(dst string, md EdgeMetadata) Node {
result := n.Copy()
result.Adjacency = result.Adjacency.Add(dst)
result.Edges[dst] = md
result.Edges = result.Edges.Add(dst, md)
return result
}

View File

@@ -56,7 +56,7 @@ func TestNode(t *testing.T) {
if node.Adjacency[0] != "foo" {
t.Errorf("want foo, have %v", node.Adjacency)
}
if *node.Edges["foo"].EgressPacketCount != 13 {
if v, ok := node.Edges.Lookup("foo"); ok && *v.EgressPacketCount != 13 {
t.Errorf("want 13, have %v", node.Edges)
}
}

View File

@@ -97,11 +97,11 @@ func (t Topology) Validate() error {
}
// Check all the edge metadatas have entries in adjacencies
for dstNodeID := range nmd.Edges {
nmd.Edges.ForEach(func(dstNodeID string, _ EdgeMetadata) {
if _, ok := t.Nodes[dstNodeID]; !ok {
errs = append(errs, fmt.Sprintf("node %s missing for edge %q", dstNodeID, nodeID))
}
}
})
}
if len(errs) > 0 {