Compare commits

..

12 Commits

Author SHA1 Message Date
amery 751e2cc4f1 build-sys: use local asciigoat.org/ini
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:57:59 +00:00
amery 71e9a5ab8a Merge branch 'pr-amery-dns' into next-amery
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:57:54 +00:00
amery bf673093c5 jpictl: introduce jpictl dns write command
it renders BIND config to describe the public view of the cluster

Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:56:07 +00:00
amery 1a47985bd7 dns: Manager.WriteTo() generates BIND config, fully qualifies
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:56:07 +00:00
amery f5ea72740c dns: introduce initial DNS Manager
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:56:07 +00:00
amery 357c85dc1a dns: SortRegions() by ISO3166
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:55:38 +00:00
amery 00aec477a4 dns: DefaultDNSProvider() using CLOUDFLARE_DNS_API_TOKEN
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:55:38 +00:00
amery e0d8592dc1 dns: introduce AddrRecord{} to abstract A/AAAA entries
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:55:38 +00:00
amery c397ca29ac cluster: introduce Region interators
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:55:38 +00:00
amery 066788b9be vscode: add Lookuper, publicsuffix and libdns to the dictionary
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-12 15:55:38 +00:00
amery 9237d7b450 Merge branch 'pr-amery-wireguard-ini' into next-amery 2023-09-12 13:23:09 +00:00
amery 4402555f04 Merge pull request 'cluster: ensure ceph monitors are set when loading a config file' (#23)
Reviewed-on: #23
2023-09-12 15:21:25 +02:00
7 changed files with 286 additions and 117 deletions
+13 -7
View File
@@ -11,6 +11,8 @@ import (
)
func newDNSManager(m *cluster.Cluster) (*dns.Manager, error) {
ctx := context.TODO()
domain := m.Domain
if m.Name != "" {
domain = m.Name + "." + domain
@@ -23,22 +25,26 @@ func newDNSManager(m *cluster.Cluster) (*dns.Manager, error) {
m.ForEachZone(func(z *cluster.Zone) bool {
z.ForEachMachine(func(p *cluster.Machine) bool {
err = mgr.AddHost(context.TODO(), z.Name, p.ID, true, p.PublicAddresses...)
err = mgr.AddHost(ctx, z.Name, p.ID, true, p.PublicAddresses...)
return err != nil
})
return err != nil
})
if err != nil {
return nil, err
}
for _, r := range m.Regions {
err := mgr.AddRegion(context.TODO(), r.Name, r.Zones()...)
if err != nil {
return nil, err
}
m.ForEachRegion(func(r *cluster.Region) bool {
r.ForEachZone(func(z *cluster.Zone) bool {
err = mgr.AddRegion(ctx, r.Name, z.Name)
return err != nil
})
return err != nil
})
if err != nil {
return nil, err
}
return mgr, nil
+39 -6
View File
@@ -1,5 +1,10 @@
package cluster
var (
_ MachineIterator = (*Region)(nil)
_ ZoneIterator = (*Region)(nil)
)
// Region represents a group of zones geographically related
type Region struct {
m *Cluster
@@ -9,13 +14,41 @@ type Region struct {
Regions []string `json:",omitempty" yaml:",omitempty"`
}
// Zones ...
func (r *Region) Zones() []string {
out := make([]string, len(r.zones))
for i := range r.zones {
out[i] = r.zones[i].Name
// ForEachRegion calls a function for each Region of the cluster
// until instructed to terminate the loop
func (m *Cluster) ForEachRegion(fn func(r *Region) bool) {
for i := range m.Regions {
r := &m.Regions[i]
if fn(r) {
return
}
}
}
// ForEachMachine calls a function for each Machine in the region
// until instructed to terminate the loop
func (r *Region) ForEachMachine(fn func(*Machine) bool) {
r.ForEachZone(func(z *Zone) bool {
var term bool
z.ForEachMachine(func(p *Machine) bool {
term = fn(p)
return term
})
return term
})
}
// ForEachZone calls a function for each Zone in the region
// until instructed to terminate the loop
func (r *Region) ForEachZone(fn func(*Zone) bool) {
for _, p := range r.zones {
if fn(p) {
// terminate
return
}
}
return out
}
func (m *Cluster) initRegions(_ *ScanOptions) error {
+25 -21
View File
@@ -1,34 +1,38 @@
// Package dns manages DNS entries for the cluster
package dns
import "net/netip"
import (
"fmt"
"net/netip"
)
// // A Config defines a Region
//
// type Config struct {
// // Name is the identifier of this Region
// Name string
// // Regions are a list of (sub)regions that belong to this Region
// Regions []string
// // Zones are a list of Zones that directly belong to this Region
// Zones []string
// }
//
// type Region struct {
// Name string
// }
// Zone represents a set of machines with high affinity
// Zone represents a set of hosts with high affinity
type Zone struct {
Name string
Machines map[int]*Machine
Hosts map[int]*Host
}
// Machine represents a member of the cluster
type Machine struct {
ID int
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)
}
-41
View File
@@ -1,41 +0,0 @@
package dns
import (
"context"
"io/fs"
"net/netip"
)
// AddHost registers a machine
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,
Machines: make(map[int]*Machine),
}
mgr.zones[zone] = z
}
z.Machines[id] = &Machine{
ID: id,
Active: active,
Addrs: SortAddrSlice(addrs),
}
return nil
}
// AddRegion specifies a new region and the zones it contains
func (mgr *Manager) AddRegion(_ context.Context, region string, zones ...string) error {
mgr.l.Debug().WithField("region", region).WithField("zones", zones).Print()
mgr.regions[region] = append(mgr.regions[region], zones...)
return nil
}
+60 -11
View File
@@ -1,9 +1,11 @@
package dns
import (
"context"
"errors"
"io/fs"
"net/netip"
"strings"
"sync"
"darvaza.org/core"
"darvaza.org/slog"
@@ -13,8 +15,6 @@ import (
// Manager is a DNS Manager instance
type Manager struct {
mu sync.Mutex
domain string
suffix string
zones map[string]*Zone
@@ -29,10 +29,7 @@ type ManagerOption func(*Manager) error
func newErrorManagerOption(err error, hint string) ManagerOption {
return func(*Manager) error {
if hint != "" {
err = core.Wrap(err, hint)
}
return err
return core.Wrap(err, hint)
}
}
@@ -77,9 +74,6 @@ func (mgr *Manager) setDefaults() error {
return errors.New("domain not specified")
}
mgr.zones = make(map[string]*Zone)
mgr.regions = make(map[string][]string)
for _, opt := range opts {
if err := opt(mgr); err != nil {
return err
@@ -109,7 +103,10 @@ func WithDomain(domain string) ManagerOption {
// NewManager creates a DNS manager with the
func NewManager(opts ...ManagerOption) (*Manager, error) {
mgr := new(Manager)
mgr := &Manager{
zones: make(map[string]*Zone),
regions: make(map[string][]string),
}
for _, opt := range opts {
if err := opt(mgr); err != nil {
@@ -122,3 +119,55 @@ func NewManager(opts ...ManagerOption) (*Manager, error) {
}
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
}
+107 -16
View File
@@ -1,7 +1,9 @@
package dns
import (
"bytes"
"fmt"
"io"
"net/netip"
"sort"
"time"
@@ -10,7 +12,11 @@ import (
"github.com/libdns/libdns"
)
// SortAddrSlice ...
func (mgr *Manager) fqdn(name string) string {
return fmt.Sprintf("%s.%s.", name, mgr.domain)
}
// SortAddrSlice sorts a slice of [netip.Addr]
func SortAddrSlice(s []netip.Addr) []netip.Addr {
sort.Slice(s, func(i, j int) bool {
return s[i].Less(s[j])
@@ -18,6 +24,46 @@ func SortAddrSlice(s []netip.Addr) []netip.Addr {
return s
}
// SortAddrRecords sorts a slice of [AddrRecord]
// by Name and Address
func SortAddrRecords(s []AddrRecord) []AddrRecord {
sort.Slice(s, func(i, j int) bool {
return s[i].Name < s[j].Name
})
for _, p := range s {
SortAddrSlice(p.Addr)
}
return s
}
// SortRegions sorts regions. first by length those 3-character
// or shorter, and then by length. It's mostly aimed at
// supporting ISO-3166 order
func SortRegions(regions []string) []string {
sort.Slice(regions, func(i, j int) bool {
r1, r2 := regions[i], regions[j]
switch {
case len(r1) < 4:
switch {
case len(r1) < len(r2):
return true
case len(r1) > len(r2):
return false
default:
return r1 < r2
}
case len(r2) < 4:
return false
default:
return r1 < r2
}
})
return regions
}
// AddrRecord represents an A or AAAA record
type AddrRecord struct {
Name string
@@ -44,6 +90,43 @@ func (rr *AddrRecord) Export() []libdns.Record {
return out
}
// WriteTo writes the record in BIND notation
func (rr *AddrRecord) WriteTo(w io.Writer) (int64, error) {
var total int
for _, addr := range rr.Addr {
n, err := fmt.Fprint(w,
rr.Name, "\t",
1, "\t",
core.IIf(addr.Is6(), "AAAA", "A"), "\t",
addr.String(), "\n")
switch {
case err != nil:
return 0, err
case n > 0:
total += n
}
}
return int64(total), nil
}
// String converts the record into BIND entries
func (rr *AddrRecord) String() string {
var buf bytes.Buffer
_, _ = 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
@@ -51,8 +134,8 @@ func (mgr *Manager) genAllAddrRecords() []AddrRecord {
// zones
for _, z := range mgr.zones {
// machines
s := mgr.genZoneMachineRecords(z)
// hosts
s := mgr.genZoneHostRecords(z)
out = append(out, s...)
// zone alias
@@ -68,11 +151,10 @@ func (mgr *Manager) genAllAddrRecords() []AddrRecord {
cache[name] = addrs
}
// regions
for name, zones := range mgr.regions {
for _, name := range mgr.genRegionsSorted() {
var addrs []netip.Addr
for _, z := range zones {
for _, z := range mgr.regions[name] {
addrs = append(addrs, cache[z]...)
}
@@ -85,18 +167,13 @@ func (mgr *Manager) genAllAddrRecords() []AddrRecord {
out = append(out, rec)
}
// sort
sort.Slice(out, func(i, j int) bool {
return out[i].Name < out[j].Name
})
return out
}
func (*Manager) genZoneAddresses(z *Zone) []netip.Addr {
var out []netip.Addr
for _, p := range z.Machines {
for _, p := range z.Hosts {
if p.Active {
out = append(out, p.Addrs...)
}
@@ -106,16 +183,30 @@ func (*Manager) genZoneAddresses(z *Zone) []netip.Addr {
return out
}
func (mgr *Manager) genZoneMachineRecords(z *Zone) []AddrRecord {
out := make([]AddrRecord, 0, len(z.Machines))
func (mgr *Manager) genZoneHostRecords(z *Zone) []AddrRecord {
out := make([]AddrRecord, 0, len(z.Hosts))
for _, p := range z.Machines {
for _, p := range z.Hosts {
rec := AddrRecord{
Name: fmt.Sprintf("%s-%v%s", z.Name, p.ID, mgr.suffix),
Name: p.String() + mgr.suffix,
Addr: p.Addrs,
}
out = append(out, rec)
}
SortAddrRecords(out)
return out
}
func (mgr *Manager) genRegionAddressesCached(name string,
zones map[string][]netip.Addr) []netip.Addr {
//
var addrs []netip.Addr
for _, zoneName := range mgr.regions[name] {
addrs = append(addrs, zones[zoneName]...)
}
SortAddrSlice(addrs)
return addrs
}
+42 -15
View File
@@ -4,32 +4,59 @@ import (
"bytes"
"fmt"
"io"
"time"
"github.com/libdns/libdns"
"net/netip"
)
// WriteTo writes the DNS data for the cluster
func (mgr *Manager) WriteTo(w io.Writer) (int64, error) {
var buf bytes.Buffer
records := mgr.genAllAddrRecords()
for i := range records {
r := &records[i]
r.Name = fmt.Sprintf("%s.%s.", r.Name, mgr.domain)
cache := make(map[string][]netip.Addr)
// zones
for _, z := range mgr.zones {
mgr.writeZoneHosts(&buf, z)
// zone alias
addrs := mgr.genZoneAddresses(z)
zoneName := z.Name
rr := AddrRecord{
Name: mgr.fqdn(zoneName + mgr.suffix),
Addr: addrs,
}
_, _ = rr.WriteTo(&buf)
// and cache for regions
cache[zoneName] = addrs
}
for _, rr1 := range records {
for _, rr2 := range rr1.Export() {
writeRecord(&buf, rr2)
}
// regions, sorted
for _, name := range mgr.genRegionsSorted() {
addrs := mgr.genRegionAddressesCached(name, cache)
mgr.writeRegionAddresses(&buf, name, addrs)
}
return buf.WriteTo(w)
}
func writeRecord(w io.Writer, rr libdns.Record) {
_, _ = fmt.Fprintf(w, "%s\t%v\tIN\t%s\t%s\n",
rr.Name, int(rr.TTL/time.Second),
rr.Type, rr.Value)
func (mgr *Manager) writeZoneHosts(w io.Writer, z *Zone) {
_, _ = fmt.Fprintf(w, ";\n; %s\n;\n", z.Name)
for _, rr := range mgr.genZoneHostRecords(z) {
rr.Name = mgr.fqdn(rr.Name)
_, _ = rr.WriteTo(w)
}
}
func (mgr *Manager) writeRegionAddresses(w io.Writer, name string, addrs []netip.Addr) {
_, _ = fmt.Fprintf(w, "; %s\n", name)
rr := AddrRecord{
Name: mgr.fqdn(name + mgr.suffix),
Addr: addrs,
}
_, _ = rr.WriteTo(w)
}