refactor: add a generic MergeActiveControls method

Use this instead of hard-coded knowledge about expected
controls and counts when merging host topology.
This commit is contained in:
Bryan Boreham
2021-05-18 14:07:35 +00:00
parent dacfd66e4d
commit c6def0a92b
2 changed files with 58 additions and 27 deletions

View File

@@ -143,6 +143,37 @@ func (n Node) ActiveControls() []string {
return strings.Split(activeControls, ScopeDelim)
}
// MergeActiveControls merges the control lists from n and b
func (n Node) MergeActiveControls(b Node) Node {
activeControlsB, tsB, foundB := b.Latest.LookupEntry(NodeActiveControls)
if !foundB { // nothing to merge
return n
}
activeControlsA, tsA, foundA := n.Latest.LookupEntry(NodeActiveControls)
if !foundA {
return n.WithLatest(NodeActiveControls, tsB, activeControlsB)
}
if activeControlsA == activeControlsB {
return n
}
// If we get here we have active controls in both n and b that are different
merged := make(map[string]struct{})
for _, c := range strings.Split(activeControlsA, ScopeDelim) {
merged[c] = struct{}{}
}
for _, c := range strings.Split(activeControlsB, ScopeDelim) {
merged[c] = struct{}{}
}
cs := make([]string, 0, len(merged))
for c := range merged {
cs = append(cs, c)
}
if tsA.Before(tsB) {
tsA = tsB
}
return n.WithLatest(NodeActiveControls, tsA, strings.Join(cs, ScopeDelim))
}
// WithParent returns a fresh copy of n, with one parent added
func (n Node) WithParent(key, parent string) Node {
n.Parents = n.Parents.AddString(key, parent)
@@ -200,33 +231,7 @@ func (n Node) Merge(other Node) Node {
// Special case to merge controls from two different probes.
if topology == "host" {
controlString, _ := newNode.Latest.Lookup(NodeActiveControls)
activeControls := strings.Split(controlString, ScopeDelim)
// Host topology should have only two active controls.
// Length equals to 2 means controls were merged properly.
if len(activeControls) != 2 {
// Check if host_exec control is missing.
if !StringSet(activeControls).Contains("host_exec") {
activeControls = append(activeControls, "host_exec")
return newNode.
WithLatestActiveControls(activeControls...)
}
// Else control kubernetes_cordon_node or kubernetes_uncordon_node is missing.
// Merge controls from current node.
c, _ := n.Latest.Lookup(NodeActiveControls)
x := strings.Split(c, ScopeDelim)
activeControls = append(activeControls, x...)
// Merge controls from incoming node.
c, _ = other.Latest.Lookup(NodeActiveControls)
x = strings.Split(c, ScopeDelim)
activeControls = append(activeControls, x...)
// Use a StringSet to avoid duplicate controls from current and incoming node.
var final StringSet
return newNode.
WithLatestActiveControls(final.Add(activeControls...)...)
}
newNode = newNode.MergeActiveControls(other)
}
return newNode

View File

@@ -1,6 +1,7 @@
package report_test
import (
"sort"
"testing"
"time"
@@ -180,3 +181,28 @@ func TestCounters(t *testing.T) {
t.Errorf("Counters: %s", test.Diff(want, have))
}
}
func TestActiveControls(t *testing.T) {
mtime.NowForce(time.Now())
defer mtime.NowReset()
controls1 := []string{"bar", "foo"}
node1 := report.MakeNode("node1").WithLatestActiveControls(controls1...)
assert.Equal(t, controls1, node1.ActiveControls())
assert.Equal(t, controls1, sorted(node1.MergeActiveControls(node1).ActiveControls()))
node2 := report.MakeNode("node2")
assert.Equal(t, controls1, node1.MergeActiveControls(node2).ActiveControls())
assert.Equal(t, controls1, node2.MergeActiveControls(node1).ActiveControls())
controls2 := []string{"bar", "bor"}
controls3 := []string{"bar", "bor", "foo"}
node3 := report.MakeNode("node1").WithLatestActiveControls(controls2...)
assert.Equal(t, controls3, sorted(node1.MergeActiveControls(node3).ActiveControls()))
assert.Equal(t, controls3, sorted(node3.MergeActiveControls(node1).ActiveControls()))
}
func sorted(s []string) []string {
sort.Strings(s)
return s
}