dns: introduce initial DNS Manager
Signed-off-by: Alejandro Mery <amery@jpi.io>
This commit is contained in:
@@ -16,6 +16,7 @@ require (
|
||||
github.com/mgechev/revive v1.3.3
|
||||
github.com/spf13/cobra v1.7.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/net v0.14.0
|
||||
gopkg.in/gcfg.v1 v1.2.3
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -41,7 +42,6 @@ require (
|
||||
github.com/rs/zerolog v1.30.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.12.0 // indirect
|
||||
|
||||
@@ -1,2 +1,38 @@
|
||||
// Package dns manages DNS entries for the cluster
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// Zone represents a set of hosts with high affinity
|
||||
type Zone struct {
|
||||
Name string
|
||||
|
||||
Hosts map[int]*Host
|
||||
}
|
||||
|
||||
func (z *Zone) String() string {
|
||||
if z == nil {
|
||||
return "undetermined"
|
||||
}
|
||||
return z.Name
|
||||
}
|
||||
|
||||
// Host represents a member of the cluster
|
||||
type Host struct {
|
||||
zone *Zone
|
||||
|
||||
ID int
|
||||
Active bool
|
||||
Addrs []netip.Addr
|
||||
}
|
||||
|
||||
func (p *Host) String() string {
|
||||
if p == nil {
|
||||
return "undetermined"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s-%v", p.zone, p.ID)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"darvaza.org/core"
|
||||
"darvaza.org/slog"
|
||||
"git.jpi.io/amery/jpictl/pkg/cluster"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Manager is a DNS Manager instance
|
||||
type Manager struct {
|
||||
domain string
|
||||
suffix string
|
||||
zones map[string]*Zone
|
||||
regions map[string][]string
|
||||
|
||||
p Provider
|
||||
l slog.Logger
|
||||
}
|
||||
|
||||
// ManagerOption configures a Manager
|
||||
type ManagerOption func(*Manager) error
|
||||
|
||||
func newErrorManagerOption(err error, hint string) ManagerOption {
|
||||
return func(*Manager) error {
|
||||
return core.Wrap(err, hint)
|
||||
}
|
||||
}
|
||||
|
||||
// WithProvider attaches a libdns Provider to the Manager
|
||||
func WithProvider(p Provider) ManagerOption {
|
||||
var err error
|
||||
|
||||
if p == nil {
|
||||
p, err = DefaultDNSProvider()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return newErrorManagerOption(err, "WithProvider")
|
||||
}
|
||||
|
||||
return func(mgr *Manager) error {
|
||||
mgr.p = p
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger attaches a logger to the Manager
|
||||
func WithLogger(log slog.Logger) ManagerOption {
|
||||
if log == nil {
|
||||
log = cluster.DefaultLogger()
|
||||
}
|
||||
|
||||
return func(mgr *Manager) error {
|
||||
mgr.l = log
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (mgr *Manager) setDefaults() error {
|
||||
var opts []ManagerOption
|
||||
|
||||
if mgr.l == nil {
|
||||
opts = append(opts, WithLogger(nil))
|
||||
}
|
||||
|
||||
if mgr.domain == "" || mgr.suffix == "" {
|
||||
return errors.New("domain not specified")
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(mgr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithDomain specifies where the manager operates
|
||||
func WithDomain(domain string) ManagerOption {
|
||||
base, err := publicsuffix.EffectiveTLDPlusOne(domain)
|
||||
if err != nil {
|
||||
return newErrorManagerOption(err, "publicsuffix")
|
||||
}
|
||||
|
||||
suffix := strings.TrimSuffix(domain, base)
|
||||
if suffix != "" {
|
||||
suffix = "." + suffix[:len(suffix)-1]
|
||||
}
|
||||
|
||||
return func(mgr *Manager) error {
|
||||
mgr.domain = base
|
||||
mgr.suffix = suffix
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewManager creates a DNS manager with the
|
||||
func NewManager(opts ...ManagerOption) (*Manager, error) {
|
||||
mgr := &Manager{
|
||||
zones: make(map[string]*Zone),
|
||||
regions: make(map[string][]string),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(mgr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := mgr.setDefaults(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mgr, nil
|
||||
}
|
||||
|
||||
// AddHost registers a host
|
||||
func (mgr *Manager) AddHost(_ context.Context, zone string, id int,
|
||||
active bool, addrs ...netip.Addr) error {
|
||||
//
|
||||
if zone == "" || id <= 0 {
|
||||
return fs.ErrInvalid
|
||||
}
|
||||
|
||||
z, ok := mgr.zones[zone]
|
||||
if !ok {
|
||||
z = &Zone{
|
||||
Name: zone,
|
||||
Hosts: make(map[int]*Host),
|
||||
}
|
||||
|
||||
mgr.zones[zone] = z
|
||||
}
|
||||
|
||||
p := &Host{
|
||||
zone: z,
|
||||
ID: id,
|
||||
Active: active,
|
||||
Addrs: SortAddrSlice(addrs),
|
||||
}
|
||||
z.Hosts[id] = p
|
||||
|
||||
if log, ok := mgr.l.Debug().WithEnabled(); ok {
|
||||
log.WithField("subsystem", "dns").
|
||||
WithField("zone", zone).
|
||||
WithField("host", p.String()).
|
||||
WithField("active", active).
|
||||
Print()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRegion specifies a new region and the zones it contains
|
||||
func (mgr *Manager) AddRegion(_ context.Context, region string, zones ...string) error {
|
||||
mgr.regions[region] = append(mgr.regions[region], zones...)
|
||||
|
||||
if log, ok := mgr.l.Debug().WithEnabled(); ok {
|
||||
for _, zoneName := range zones {
|
||||
log.WithField("subsystem", "dns").
|
||||
WithField("region", region).
|
||||
WithField("zone", zoneName).Print()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -113,3 +113,83 @@ func (rr *AddrRecord) String() string {
|
||||
_, _ = rr.WriteTo(&buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (mgr *Manager) genRegionsSorted() []string {
|
||||
regions := make([]string, 0, len(mgr.regions))
|
||||
for name := range mgr.regions {
|
||||
regions = append(regions, name)
|
||||
}
|
||||
|
||||
return SortRegions(regions)
|
||||
}
|
||||
|
||||
func (mgr *Manager) genAllAddrRecords() []AddrRecord {
|
||||
var out []AddrRecord
|
||||
|
||||
cache := make(map[string][]netip.Addr)
|
||||
|
||||
// zones
|
||||
for _, z := range mgr.zones {
|
||||
// hosts
|
||||
s := mgr.genZoneHostRecords(z)
|
||||
out = append(out, s...)
|
||||
|
||||
// zone alias
|
||||
addrs := mgr.genZoneAddresses(z)
|
||||
name := z.Name
|
||||
|
||||
out = append(out, AddrRecord{
|
||||
Name: name + mgr.suffix,
|
||||
Addr: addrs,
|
||||
})
|
||||
|
||||
// and cache for regions
|
||||
cache[name] = addrs
|
||||
}
|
||||
|
||||
for _, name := range mgr.genRegionsSorted() {
|
||||
var addrs []netip.Addr
|
||||
|
||||
for _, z := range mgr.regions[name] {
|
||||
addrs = append(addrs, cache[z]...)
|
||||
}
|
||||
|
||||
rec := AddrRecord{
|
||||
Name: name + mgr.suffix,
|
||||
Addr: addrs,
|
||||
}
|
||||
rec.Sort()
|
||||
|
||||
out = append(out, rec)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (*Manager) genZoneAddresses(z *Zone) []netip.Addr {
|
||||
var out []netip.Addr
|
||||
|
||||
for _, p := range z.Hosts {
|
||||
if p.Active {
|
||||
out = append(out, p.Addrs...)
|
||||
}
|
||||
}
|
||||
|
||||
SortAddrSlice(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func (mgr *Manager) genZoneHostRecords(z *Zone) []AddrRecord {
|
||||
out := make([]AddrRecord, 0, len(z.Hosts))
|
||||
|
||||
for _, p := range z.Hosts {
|
||||
rec := AddrRecord{
|
||||
Name: p.String() + mgr.suffix,
|
||||
Addr: p.Addrs,
|
||||
}
|
||||
out = append(out, rec)
|
||||
}
|
||||
|
||||
SortAddrRecords(out)
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user