Files
coredns/plugin/dynupdate/benchmark_test.go
houyuwushang 5aa4dc2941 plugin/dynupdate: add durable authenticated RFC 2136 updates (#8520)
* plugin/dynupdate: add authenticated RFC 2136 updates

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>

* plugin/dynupdate: fix README test fixtures

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>

* test: format README fixture map

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>

* plugin/dynupdate: persist updates and bound writable zones

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>

* plugin/dynupdate: preserve middleware and fix interoperability fixtures

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>

* test(dynupdate): validate Kea lifecycle and bounded zone costs

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>

* plugin/dynupdate: reject duplicate directives and harden client fixtures

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>

* plugin/dynupdate: fix update routing and startup validation

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>

---------

Signed-off-by: houyuwushang <liuluoqianqiu@outlook.com>
2026-09-22 00:53:26 -07:00

142 lines
4.0 KiB
Go

package dynupdate
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/coredns/coredns/plugin/pkg/dnstest"
coretest "github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
)
// BenchmarkUpdate measures a bounded, changing zone rather than duplicate
// no-op updates. Durable mode uses bbolt's normal synchronous commit path.
func BenchmarkUpdate(b *testing.B) {
for _, count := range []int{100, 1000, defaultMaxRecords} {
for _, durable := range []bool{false, true} {
for _, readers := range []int{0, 4} {
b.Run(fmt.Sprintf("records=%d/durable=%t/readers=%d", count, durable, readers), func(b *testing.B) {
d := benchmarkZone(b, count, durable)
updates := make([][]dns.RR, 2)
for i := range updates {
rr, err := dns.NewRR(fmt.Sprintf("change.example.org. 60 IN A 192.0.2.%d", i+1))
if err != nil {
b.Fatal(err)
}
m := new(dns.Msg).SetUpdate(testZone)
m.RemoveRRset([]dns.RR{rr})
m.Insert([]dns.RR{rr})
updates[i] = m.Ns
}
var queries, queryNanos, maxQueryNanos atomic.Int64
var wg sync.WaitGroup
startQueries := make(chan struct{})
stop := make(chan struct{})
errors := make(chan error, readers)
for range readers {
wg.Go(func() {
query := new(dns.Msg).SetQuestion("change.example.org.", dns.TypeA)
<-startQueries
for {
select {
case <-stop:
return
default:
}
start := time.Now()
w := dnstest.NewRecorder(&coretest.ResponseWriter{})
code, err := d.ServeDNS(context.Background(), w, query)
if err != nil || code != dns.RcodeSuccess || w.Msg == nil || len(w.Msg.Answer) != 1 {
errors <- fmt.Errorf("concurrent query: code=%d err=%v reply=%v", code, err, w.Msg)
return
}
latency := time.Since(start).Nanoseconds()
queries.Add(1)
queryNanos.Add(latency)
for old := maxQueryNanos.Load(); latency > old; old = maxQueryNanos.Load() {
if maxQueryNanos.CompareAndSwap(old, latency) {
break
}
}
}
})
}
b.ReportAllocs()
b.ResetTimer()
start := time.Now()
close(startQueries)
for i := range b.N {
code, err := d.applyUpdate(testKey, nil, updates[i%2])
if code != dns.RcodeSuccess || err != nil {
b.Errorf("update: code=%d err=%v", code, err)
break
}
}
b.StopTimer()
close(stop)
wg.Wait()
elapsed := time.Since(start)
close(errors)
for err := range errors {
b.Error(err)
}
if n := queries.Load(); n > 0 {
b.ReportMetric(float64(queryNanos.Load())/float64(n), "ns/query")
b.ReportMetric(float64(maxQueryNanos.Load()), "max-ns/query")
b.ReportMetric(float64(n)/elapsed.Seconds(), "queries/s")
}
})
}
}
}
}
func benchmarkZone(b *testing.B, count int, durable bool) *DynUpdate {
b.Helper()
texts := []string{
"example.org. 60 IN SOA ns.example.org. hostmaster.example.org. 10 60 60 60 60",
"example.org. 60 IN NS ns.example.org.",
"change.example.org. 60 IN A 192.0.2.254",
}
for i := len(texts); i < count; i++ {
texts = append(texts, fmt.Sprintf("host-%d.example.org. 60 IN A 192.0.2.100", i))
}
d := &DynUpdate{Zone: testZone, permissions: []permission{{key: testKey, name: allNames, allTypes: true}}}
for _, text := range texts {
rr, err := dns.NewRR(text)
if err != nil {
b.Fatal(err)
}
d.records = append(d.records, rr)
}
var err error
d.view, err = d.build(d.records)
if err != nil {
b.Fatal(err)
}
if durable {
dir := b.TempDir()
d.seed, d.database = filepath.Join(dir, "seed.zone"), filepath.Join(dir, "zone.db")
if err := os.WriteFile(d.seed, []byte(strings.Join(texts, "\n")+"\n"), 0600); err != nil {
b.Fatal(err)
}
if err := d.ensureStore(); err != nil {
b.Fatal(err)
}
}
b.Cleanup(func() {
if err := d.close(); err != nil {
b.Error(err)
}
})
return d
}