checker-resolver-propagation/checker/consensus.go

123 lines
2.6 KiB
Go

package checker
import (
"sort"
)
// Idempotent: rules and report both call it; both must see the same grouping.
func deriveView(data *ResolverPropagationData) {
if data == nil {
return
}
for key, view := range data.RRsets {
// Reset derived fields so repeated calls stay idempotent.
view.Groups = nil
view.ConsensusSig = ""
view.Agreeing = nil
view.Dissenting = nil
view.MatchesExpected = false
voteCount := map[string]int{}
type group struct {
rcode string
records []string
resolvers []string
}
groups := map[string]*group{}
for _, rv := range data.Resolvers {
p := rv.Probes[key]
if p == nil || p.Error != "" {
continue
}
g := groups[p.Signature]
if g == nil {
g = &group{rcode: p.Rcode, records: p.Records}
groups[p.Signature] = g
}
g.resolvers = append(g.resolvers, rv.ID)
if !rv.Filtered {
voteCount[p.Signature]++
}
}
// Pick the winning signature, preferring NOERROR responses.
var winSig string
var winVotes int
for sig, g := range groups {
if g.rcode != "NOERROR" && winSig != "" {
continue
}
if voteCount[sig] > winVotes {
winSig = sig
winVotes = voteCount[sig]
}
}
if winSig == "" {
for sig := range groups {
winSig = sig
break
}
}
view.ConsensusSig = winSig
type gEntry struct {
sig string
g *group
}
var entries []gEntry
for s, g := range groups {
sort.Strings(g.resolvers)
entries = append(entries, gEntry{sig: s, g: g})
}
sort.Slice(entries, func(i, j int) bool {
return len(entries[i].g.resolvers) > len(entries[j].g.resolvers)
})
for _, e := range entries {
view.Groups = append(view.Groups, SignatureGroup{
Signature: e.sig,
Records: e.g.records,
Resolvers: e.g.resolvers,
Rcode: e.g.rcode,
})
if e.sig == winSig {
view.Agreeing = append(view.Agreeing, e.g.resolvers...)
} else {
view.Dissenting = append(view.Dissenting, e.g.resolvers...)
}
}
sort.Strings(view.Agreeing)
sort.Strings(view.Dissenting)
if view.Expected != "" {
view.MatchesExpected = view.ConsensusSig == view.Expected
}
}
// Recompute UnfilteredAgreeing from the consensus we just built.
agree := 0
for _, rv := range data.Resolvers {
if rv.Filtered || !rv.Reachable {
continue
}
ok := true
for key, p := range rv.Probes {
if p == nil || p.Error != "" {
continue
}
v := data.RRsets[key]
if v == nil || v.ConsensusSig == "" {
continue
}
if p.Signature != v.ConsensusSig {
ok = false
break
}
}
if ok {
agree++
}
}
data.Stats.UnfilteredAgreeing = agree
}