feat(dns): add fallback discoverer for dns.discover
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
acd6d70ac2
commit
9919c70ed1
2 changed files with 117 additions and 16 deletions
55
service.go
55
service.go
|
|
@ -11,10 +11,10 @@ import (
|
|||
)
|
||||
|
||||
type NameRecords struct {
|
||||
A []string `json:"a"`
|
||||
AAAA []string `json:"aaaa"`
|
||||
TXT []string `json:"txt"`
|
||||
NS []string `json:"ns"`
|
||||
A []string `json:"a"`
|
||||
AAAA []string `json:"aaaa"`
|
||||
TXT []string `json:"txt"`
|
||||
NS []string `json:"ns"`
|
||||
}
|
||||
|
||||
type ResolveAllResult struct {
|
||||
|
|
@ -29,16 +29,18 @@ type ResolveAddressResult struct {
|
|||
}
|
||||
|
||||
type Service struct {
|
||||
mu sync.RWMutex
|
||||
records map[string]NameRecords
|
||||
reverseIndex map[string][]string
|
||||
treeRoot string
|
||||
discoverer func() (map[string]NameRecords, error)
|
||||
mu sync.RWMutex
|
||||
records map[string]NameRecords
|
||||
reverseIndex map[string][]string
|
||||
treeRoot string
|
||||
discoverer func() (map[string]NameRecords, error)
|
||||
fallbackDiscoverer func() (map[string]NameRecords, error)
|
||||
}
|
||||
|
||||
type ServiceOptions struct {
|
||||
Records map[string]NameRecords
|
||||
Discoverer func() (map[string]NameRecords, error)
|
||||
Records map[string]NameRecords
|
||||
Discoverer func() (map[string]NameRecords, error)
|
||||
FallbackDiscoverer func() (map[string]NameRecords, error)
|
||||
}
|
||||
|
||||
func NewService(options ServiceOptions) *Service {
|
||||
|
|
@ -48,24 +50,46 @@ func NewService(options ServiceOptions) *Service {
|
|||
}
|
||||
treeRoot := computeTreeRoot(cached)
|
||||
return &Service{
|
||||
records: cached,
|
||||
reverseIndex: buildReverseIndex(cached),
|
||||
treeRoot: treeRoot,
|
||||
discoverer: options.Discoverer,
|
||||
records: cached,
|
||||
reverseIndex: buildReverseIndex(cached),
|
||||
treeRoot: treeRoot,
|
||||
discoverer: options.Discoverer,
|
||||
fallbackDiscoverer: options.FallbackDiscoverer,
|
||||
}
|
||||
}
|
||||
|
||||
func (service *Service) Discover() error {
|
||||
discoverer := service.discoverer
|
||||
fallback := service.fallbackDiscoverer
|
||||
if discoverer == nil {
|
||||
if fallback == nil {
|
||||
return nil
|
||||
}
|
||||
discovered, err := fallback()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service.replaceRecords(discovered)
|
||||
return nil
|
||||
}
|
||||
|
||||
discovered, err := discoverer()
|
||||
if err != nil {
|
||||
if fallback == nil {
|
||||
return err
|
||||
}
|
||||
discovered, err = fallback()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service.replaceRecords(discovered)
|
||||
return err
|
||||
}
|
||||
service.replaceRecords(discovered)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) replaceRecords(discovered map[string]NameRecords) {
|
||||
cached := make(map[string]NameRecords, len(discovered))
|
||||
for name, record := range discovered {
|
||||
normalizedName := normalizeName(name)
|
||||
|
|
@ -80,7 +104,6 @@ func (service *Service) Discover() error {
|
|||
service.records = cached
|
||||
service.reverseIndex = buildReverseIndex(service.records)
|
||||
service.treeRoot = computeTreeRoot(service.records)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) SetRecord(name string, record NameRecords) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -254,6 +255,83 @@ func TestServiceDiscoverReplacesRecordsFromDiscoverer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServiceDiscoverFallsBackWhenPrimaryDiscovererFails(t *testing.T) {
|
||||
primaryCalled := false
|
||||
fallbackCalled := false
|
||||
|
||||
service := NewService(ServiceOptions{
|
||||
Records: map[string]NameRecords{
|
||||
"legacy.charon.lthn": {
|
||||
A: []string{"10.11.11.11"},
|
||||
},
|
||||
},
|
||||
Discoverer: func() (map[string]NameRecords, error) {
|
||||
primaryCalled = true
|
||||
return nil, errors.New("chain service unavailable")
|
||||
},
|
||||
FallbackDiscoverer: func() (map[string]NameRecords, error) {
|
||||
fallbackCalled = true
|
||||
return map[string]NameRecords{
|
||||
"gateway.charon.lthn": {
|
||||
A: []string{"10.10.10.10"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
|
||||
if err := service.Discover(); err != nil {
|
||||
t.Fatalf("expected fallback discovery to succeed: %v", err)
|
||||
}
|
||||
if !primaryCalled {
|
||||
t.Fatal("expected primary discoverer to be attempted")
|
||||
}
|
||||
if !fallbackCalled {
|
||||
t.Fatal("expected fallback discoverer to run after primary failure")
|
||||
}
|
||||
|
||||
result, ok := service.Resolve("gateway.charon.lthn")
|
||||
if !ok {
|
||||
t.Fatal("expected fallback record to resolve")
|
||||
}
|
||||
if len(result.A) != 1 || result.A[0] != "10.10.10.10" {
|
||||
t.Fatalf("unexpected fallback resolve result: %#v", result.A)
|
||||
}
|
||||
if _, ok := service.Resolve("legacy.charon.lthn"); ok {
|
||||
t.Fatal("expected legacy record to be replaced by fallback discovery")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceDiscoverUsesFallbackOnlyWhenPrimaryMissing(t *testing.T) {
|
||||
fallbackCalled := false
|
||||
|
||||
service := NewService(ServiceOptions{
|
||||
Records: map[string]NameRecords{
|
||||
"legacy.charon.lthn": {
|
||||
A: []string{"10.11.11.11"},
|
||||
},
|
||||
},
|
||||
FallbackDiscoverer: func() (map[string]NameRecords, error) {
|
||||
fallbackCalled = true
|
||||
return map[string]NameRecords{
|
||||
"gateway.charon.lthn": {
|
||||
A: []string{"10.10.10.20"},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
|
||||
if err := service.Discover(); err != nil {
|
||||
t.Fatalf("expected fallback discovery to run: %v", err)
|
||||
}
|
||||
if !fallbackCalled {
|
||||
t.Fatal("expected fallback discoverer to run when primary is missing")
|
||||
}
|
||||
|
||||
if _, ok := service.Resolve("gateway.charon.lthn"); !ok {
|
||||
t.Fatal("expected fallback record to resolve")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceDiscoverReturnsNilWithoutDiscoverer(t *testing.T) {
|
||||
service := NewService(ServiceOptions{
|
||||
Records: map[string]NameRecords{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue