Compare commits

..

1 Commits

Author SHA1 Message Date
amery 6c875ae832 cluster: introduce NodeID and ZoneID types
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-31 14:54:07 +01:00
23 changed files with 314 additions and 809 deletions
+1 -2
View File
@@ -15,8 +15,7 @@ TMPDIR ?= .tmp
REVIVE ?= $(GOBIN)/revive
REVIVE_CONF ?= $(TOOLSDIR)/revive.toml
REVIVE_RUN_ARGS ?= -config $(REVIVE_CONF) -formatter friendly
REVIVE_VERSION ?= v1.3.7
REVIVE_INSTALL_URL ?= github.com/mgechev/revive@$(REVIVE_VERSION)
REVIVE_INSTALL_URL ?= github.com/mgechev/revive@master
GO_INSTALL_URLS = \
$(REVIVE_INSTALL_URL) \
+6 -6
View File
@@ -4,7 +4,7 @@ go 1.19
require (
asciigoat.org/ini v0.2.5
darvaza.org/core v0.14.2
darvaza.org/core v0.13.1
darvaza.org/resolver v0.9.2
darvaza.org/sidecar v0.4.0
darvaza.org/slog v0.5.7
@@ -15,8 +15,8 @@ require (
github.com/libdns/libdns v0.2.2
github.com/mgechev/revive v1.3.7
github.com/spf13/cobra v1.8.0
golang.org/x/crypto v0.25.0
golang.org/x/net v0.27.0
golang.org/x/crypto v0.23.0
golang.org/x/net v0.25.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -46,7 +46,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
)
+13 -12
View File
@@ -4,8 +4,8 @@ asciigoat.org/ini v0.2.5 h1:4gRIp9rU+XQt8+HMqZO5R7GavMv9Yl2+N+je6djDIAE=
asciigoat.org/ini v0.2.5/go.mod h1:gmXzJ9XFqf1NLk5nQkj04USQ4tMtdRJHNQX6vp3DzjU=
darvaza.org/cache/x/simplelru v0.1.8 h1:rvFucut4wKYbsYc994yR3P0M08NqlsvZxr5G4QK82tw=
darvaza.org/cache/x/simplelru v0.1.8/go.mod h1:Mv1isOJTcXYK+aK0AvUe+/3KpRTXDsYga6rdTS/upNs=
darvaza.org/core v0.14.2 h1:6p0iznuGfVGbBp+CnkZTw1b76j6Q/j4ffDztZXrrlK8=
darvaza.org/core v0.14.2/go.mod h1:C+B0GRNLB+/asGfxjQ9XZERdk7xaFxzt5xTIBPiNm2M=
darvaza.org/core v0.13.1 h1:ZoAfZ3OLnw+t28qMQQxXrDIkETmT2h5gAO6F1XuBpwg=
darvaza.org/core v0.13.1/go.mod h1:47Ydh67KnzjLNu1mzX3r2zpphbxQqEaihMsUq5GflQ4=
darvaza.org/resolver v0.9.2 h1:sUX6LZ1eN5TzJW7L4m7HM+BvwBeWl8dYYDGVSe+AIhk=
darvaza.org/resolver v0.9.2/go.mod h1:XWqPhrxoOKNzRuSozOwmE1M6QVqQL28jEdxylnIO8Nw=
darvaza.org/sidecar v0.4.0 h1:wHghxzLsiT82WDBBUf34aTqtOvRBg4UbxVIJgKNXRVA=
@@ -94,23 +94,24 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-43
View File
@@ -1,43 +0,0 @@
package cluster
import (
"net/netip"
"git.jpi.io/amery/jpictl/pkg/rings"
)
// RingOnePrefix returns the ring 1 subnet of this [Zone].
func (z *Zone) RingOnePrefix() netip.Prefix {
subnet, err := rings.RingOnePrefix(z.RegionID(), z.ID)
if err != nil {
panic(err)
}
return subnet
}
// RingOnePrefix returns the ring 1 subnet this [Machine] belongs
// to.
func (m *Machine) RingOnePrefix() netip.Prefix {
return m.zone.RingOnePrefix()
}
// RingZeroAddress returns the ring 0 address of the [Machine]
// if it can act as gateway.
func (m *Machine) RingZeroAddress() (netip.Addr, bool) {
addr, err := rings.RingZeroAddress(m.Region(), m.Zone(), m.ID)
if err != nil {
return netip.Addr{}, false
}
return addr, true
}
// RingOneAddress returns the ring 1 address of the [Machine]
func (m *Machine) RingOneAddress() netip.Addr {
addr, err := rings.RingOneAddress(m.Region(), m.Zone(), m.ID)
if err != nil {
panic(err)
}
return addr
}
+1 -1
View File
@@ -66,7 +66,7 @@ func (m *Cluster) GenCephConfig() (*ceph.Config, error) {
m.ForEachZone(func(z *Zone) bool {
for _, p := range z.GetCephMonitors() {
addr := p.RingOneAddress()
addr, _ := RingOneAddress(z.ID, p.ID)
cfg.Global.Monitors = append(cfg.Global.Monitors, p.Name)
cfg.Global.MonitorsAddr = append(cfg.Global.MonitorsAddr, addr)
+2 -2
View File
@@ -4,7 +4,6 @@ import (
"os"
"darvaza.org/slog"
"git.jpi.io/amery/jpictl/pkg/ceph"
)
@@ -15,7 +14,8 @@ type cephScanTODO struct {
func (todo *cephScanTODO) checkMachine(p *Machine) bool {
// on ceph all addresses are ring1
addr := p.RingOneAddress().String()
ring1, _ := RingOneAddress(p.Zone(), p.ID)
addr := ring1.String()
if _, found := todo.names[p.Name]; found {
// found on the TODO by name
+2 -4
View File
@@ -6,18 +6,16 @@ import (
"os"
"gopkg.in/yaml.v3"
"git.jpi.io/amery/jpictl/pkg/rings"
)
func (m *Cluster) init(opts *ScanOptions) error {
for _, fn := range []func(*ScanOptions) error{
m.initZones,
m.initRegions,
m.scanZoneIDs,
m.scanSort,
m.scanGateways,
m.initCephMonitors,
m.initRegions,
} {
if err := fn(opts); err != nil {
return err
@@ -47,7 +45,7 @@ func (m *Cluster) initZones(opts *ScanOptions) error {
func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error {
var hasMissing bool
var lastMachineID rings.NodeID
var lastMachineID NodeID
z.zones = m
z.logger = m
+2 -4
View File
@@ -7,8 +7,6 @@ import (
"strings"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
const (
@@ -25,9 +23,9 @@ func (m *Cluster) scan(opts *ScanOptions) error {
for _, fn := range []func(*ScanOptions) error{
m.scanDirectory,
m.scanMachines,
m.initRegions,
m.scanZoneIDs,
m.scanSort,
m.initRegions,
m.scanGateways,
m.scanCephMonitors,
} {
@@ -116,7 +114,7 @@ func (m *Cluster) scanMachines(opts *ScanOptions) error {
func (m *Cluster) scanZoneIDs(_ *ScanOptions) error {
var hasMissing bool
var lastZoneID rings.ZoneID
var lastZoneID ZoneID
m.ForEachZone(func(z *Zone) bool {
switch {
+50 -91
View File
@@ -4,11 +4,8 @@ import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
// Env is a shell environment factory for this cluster
@@ -37,49 +34,31 @@ func (m *Cluster) Env(export bool) (*Env, error) {
return env, nil
}
// Zones returns the list of Zone IDs of a region,
// or from all if none is specified.
func (m *Env) Zones(r *Region) []rings.ZoneID {
var zones []rings.ZoneID
// Zones returns the list of Zone IDs
func (m *Env) Zones() []ZoneID {
var zones []ZoneID
iter := core.IIf[ZoneIterator](r != nil, r, m)
iter.ForEachZone(func(z *Zone) bool {
m.ForEachZone(func(z *Zone) bool {
zones = append(zones, z.ID)
return false
})
core.SliceSortOrdered(zones)
return zones
}
// RegionsNames returns a sorted list of primary regions names
func (m *Env) RegionsNames() []string {
// Regions returns the list of primary regions
func (m *Env) Regions() []string {
var regions []string
m.ForEachRegion(func(r *Region) bool {
if r.IsPrimary() {
if r.Cluster != nil {
regions = append(regions, r.Name)
}
return false
})
core.SliceSortOrdered(regions)
return regions
}
// Regions returns a sorted list of primary regions IDs
func (m *Env) Regions() (regions []rings.RegionID) {
m.ForEachRegion(func(r *Region) bool {
if r.IsPrimary() {
regions = append(regions, r.ID)
}
return false
})
core.SliceSortOrdered(regions)
sort.Strings(regions)
return regions
}
@@ -91,75 +70,41 @@ func (m *Env) WriteTo(w io.Writer) (int64, error) {
m.writeEnvVar(&buf, m.cephFSID, "FSID")
}
regions := m.getRegions()
ids := core.SliceMap(regions, func(_ []rings.RegionID, r *Region) (out []rings.RegionID) {
return append(out, r.ID)
})
names := core.SliceMap(regions, func(_ []string, r *Region) (out []string) {
return append(out, r.Name)
})
m.writeEnvVar(&buf, genEnvStrings(m.Regions()), "REGIONS")
m.writeEnvVar(&buf, genEnvInts(m.Zones()), "ZONES")
m.writeEnvVar(&buf, genEnvInts(ids), "REGIONS")
m.writeEnvVar(&buf, genEnvStrings(names), "REGIONS_NAMES")
for _, r := range regions {
m.writeEnvRegion(&buf, r)
}
m.ForEachZone(func(z *Zone) bool {
m.writeEnvZone(&buf, z)
return false
})
return buf.WriteTo(w)
}
func (m *Env) getRegions() (out []*Region) {
m.ForEachRegion(func(r *Region) bool {
if r.IsPrimary() {
out = append(out, r)
}
return false
})
func (m *Env) writeEnvZone(w io.Writer, z *Zone) {
zoneID := z.ID
core.SliceSortFn(out, func(a, b *Region) bool {
return a.ID < b.ID
})
// ZONE{zoneID}
m.writeEnvVar(w, genEnvZoneNodes(z), "ZONE%v", zoneID)
return out
}
func (m *Env) writeEnvRegion(w io.Writer, r *Region) {
regionID := r.ID
// ZONE{zoneID}_NAME
m.writeEnvVar(w, z.Name, "ZONE%v_%s", zoneID, "NAME")
// REGION{regionID}_NAME
m.writeEnvVar(w, r.Name, "REGION%v_%s", regionID, "NAME")
// REGION{regionID}_ZONES
m.writeEnvVar(w, genEnvInts(m.Zones(r)), "REGION%v_%s", regionID, "ZONES")
r.ForEachZone(func(z *Zone) bool {
m.writeEnvZone(w, r, z)
return false
})
}
func (m *Env) writeEnvZone(w io.Writer, r *Region, z *Zone) {
zonePrefix := fmt.Sprintf("REGION%v_ZONE%v", r.ID, z.ID)
monPrefix := zonePrefix + "_MON"
// REGION{regionID}_ZONE{zoneID}
m.writeEnvVar(w, genEnvZoneNodes(z), zonePrefix)
// REGION{regionID}_ZONE{zoneID}_NAME
m.writeEnvVar(w, z.Name, zonePrefix+"_NAME")
// REGION{regionID}_ZONE{zoneID}_GW
// ZONE{zoneID}_GW
gateways, _ := z.GatewayIDs()
m.writeEnvVar(w, genEnvInts(gateways), zonePrefix+"_GW")
m.writeEnvVar(w, genEnvInts(gateways), "ZONE%v_%s", zoneID, "GW")
// ZONE{zoneID}_REGION
m.writeEnvVar(w, genEnvZoneRegion(z), "ZONE%v_%s", zoneID, "REGION")
// Ceph
monitors := z.GetCephMonitors()
// REGION{regionID}_MON{zone_ID}
m.writeEnvVar(w, genEnvZoneCephMonNames(monitors), monPrefix)
// REGION{regionID}_MON{zone_ID}_IP
m.writeEnvVar(w, genEnvZoneCephMonIPs(monitors), monPrefix+"_IP")
// REGION{regionID}_MON{zone_ID}_ID
m.writeEnvVar(w, genEnvZoneCephMonIDs(monitors), monPrefix+"_ID")
// MON{zoneID}_NAME
m.writeEnvVar(w, genEnvZoneCephMonNames(monitors), "MON%v_%s", zoneID, "NAME")
// MON{zoneID}_IP
m.writeEnvVar(w, genEnvZoneCephMonIPs(monitors), "MON%v_%s", zoneID, "IP")
// MON{zoneID}_ID
m.writeEnvVar(w, genEnvZoneCephMonIDs(monitors), "MON%v_%s", zoneID, "ID")
}
func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) {
@@ -184,15 +129,14 @@ func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) {
}
}
func genEnvInts[T core.Signed](values []T) string {
func genEnvInts[T ~int | ~uint](values []T) string {
var buf bytes.Buffer
for _, v := range values {
if buf.Len() > 0 {
_, _ = buf.WriteRune(' ')
}
_, _ = buf.WriteString(fmt.Sprintf("%v", int64(v)))
_, _ = buf.WriteString(fmt.Sprintf("%v", v))
}
return buf.String()
@@ -216,6 +160,21 @@ func genEnvZoneNodes(z *Zone) string {
return ""
}
func genEnvZoneRegion(z *Zone) string {
var region string
z.ForEachRegion(func(r *Region) bool {
if r.Cluster != nil {
region = r.Name
return true
}
return false
})
return region
}
func genEnvZoneCephMonNames(m Machines) string {
var buf strings.Builder
m.ForEachMachine(func(p *Machine) bool {
@@ -232,7 +191,7 @@ func genEnvZoneCephMonNames(m Machines) string {
func genEnvZoneCephMonIPs(m Machines) string {
var buf strings.Builder
m.ForEachMachine(func(p *Machine) bool {
addr := p.RingOneAddress()
addr, _ := RingOneAddress(p.Zone(), p.ID)
if buf.Len() > 0 {
_, _ = buf.WriteRune(' ')
+1 -14
View File
@@ -1,13 +1,6 @@
package cluster
import (
"errors"
"io/fs"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
import "errors"
var (
// ErrInvalidName indicates the name isn't valid
@@ -21,9 +14,3 @@ var (
// the intended purpose
ErrInvalidNode = errors.New("invalid node")
)
// ErrInvalidRing returns an error indicating the [rings.RingID]
// can't be used for the intended purpose
func ErrInvalidRing(ringID rings.RingID) error {
return core.QuietWrap(fs.ErrInvalid, "invalid ring %v", ringID)
}
+3 -3
View File
@@ -71,14 +71,14 @@ func (p *Machine) WriteHosts() error {
func (z *Zone) genHosts(out *hostsFile, p *Machine) {
var names []string
ip := p.RingOneAddress()
ip, _ := RingOneAddress(p.zone.ID, p.ID)
names = append(names, p.Name)
if p.CephMonitor {
names = append(names, fmt.Sprintf("%s-%s", p.zone.Name, "ceph"))
names = append(names, fmt.Sprintf("%s-%s", p.zone.Name, "k3s"))
if z.Is(p.Region(), p.Zone()) {
if z.ID == p.zone.ID {
names = append(names, "ceph")
names = append(names, "k3s")
}
@@ -94,7 +94,7 @@ func (z *Zone) genHosts(out *hostsFile, p *Machine) {
if p.IsGateway() {
var s string
ip, _ = p.RingZeroAddress()
ip, _ = RingZeroAddress(p.zone.ID, p.ID)
s = fmt.Sprintf("%s-%v", p.Name, 0)
entry = hostsEntry{
+5 -12
View File
@@ -3,8 +3,6 @@ package cluster
import (
"net/netip"
"strings"
"git.jpi.io/amery/jpictl/pkg/rings"
)
// revive:disable:line-length-limit
@@ -14,7 +12,7 @@ type Machine struct {
zone *Zone
logger `json:"-" yaml:"-"`
ID rings.NodeID
ID NodeID
Name string `json:"-" yaml:"-"`
Inactive bool `json:"inactive,omitempty" yaml:"inactive,omitempty"`
@@ -53,13 +51,13 @@ func (m *Machine) IsActive() bool {
// IsGateway tells if the Machine is a ring0 gateway
func (m *Machine) IsGateway() bool {
_, ok := m.getRingInfo(rings.RingZeroID)
_, ok := m.getRingInfo(0)
return ok
}
// SetGateway enables/disables a Machine ring0 integration
func (m *Machine) SetGateway(enabled bool) error {
ri, found := m.getRingInfo(rings.RingZeroID)
ri, found := m.getRingInfo(0)
switch {
case !found && !enabled:
return nil
@@ -72,19 +70,14 @@ func (m *Machine) SetGateway(enabled bool) error {
}
ri.Enabled = enabled
return m.SyncWireguardConfig(rings.RingZeroID)
return m.SyncWireguardConfig(0)
}
// Zone indicates the [Zone] this machine belongs to
func (m *Machine) Zone() rings.ZoneID {
func (m *Machine) Zone() ZoneID {
return m.zone.ID
}
// Region indicates the [Region] this machine belongs to
func (m *Machine) Region() rings.RegionID {
return m.zone.RegionID()
}
func (m *Machine) getPeerByName(name string) (*Machine, bool) {
return m.zone.zones.GetMachineByName(name)
}
+42 -82
View File
@@ -8,26 +8,18 @@ import (
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
"git.jpi.io/amery/jpictl/pkg/wireguard"
)
// GetWireguardKeys reads a wgN.key/wgN.pub files
func (m *Machine) GetWireguardKeys(ringID rings.RingID) (wireguard.KeyPair, error) {
func (m *Machine) GetWireguardKeys(ring int) (wireguard.KeyPair, error) {
var (
data []byte
err error
out wireguard.KeyPair
)
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
// invalid ring
return out, err
}
keyFile, pubFile, _ := ring.Files()
data, err = m.ReadFile(keyFile)
data, err = m.ReadFile("wg%v.key", ring)
if err != nil {
// failed to read
return out, err
@@ -36,11 +28,11 @@ func (m *Machine) GetWireguardKeys(ringID rings.RingID) (wireguard.KeyPair, erro
out.PrivateKey, err = wireguard.PrivateKeyFromBase64(string(data))
if err != nil {
// bad key
err = core.Wrap(err, keyFile)
err = core.Wrap(err, "wg%v.key", ring)
return out, err
}
data, err = m.ReadFile(pubFile)
data, err = m.ReadFile("wg%v.pub", ring)
switch {
case os.IsNotExist(err):
// no wgN.pub is fine
@@ -52,7 +44,7 @@ func (m *Machine) GetWireguardKeys(ringID rings.RingID) (wireguard.KeyPair, erro
out.PublicKey, err = wireguard.PublicKeyFromBase64(string(data))
if err != nil {
// bad key
err = core.Wrap(err, pubFile)
err = core.Wrap(err, "wg%v.pub", ring)
return out, err
}
}
@@ -61,8 +53,8 @@ func (m *Machine) GetWireguardKeys(ringID rings.RingID) (wireguard.KeyPair, erro
return out, err
}
func (m *Machine) tryReadWireguardKeys(ringID rings.RingID) error {
kp, err := m.GetWireguardKeys(ringID)
func (m *Machine) tryReadWireguardKeys(ring int) error {
kp, err := m.GetWireguardKeys(ring)
switch {
case os.IsNotExist(err):
// ignore
@@ -73,25 +65,20 @@ func (m *Machine) tryReadWireguardKeys(ringID rings.RingID) error {
default:
// import keys
ri := &RingInfo{
Ring: MustWireguardInterfaceID(ringID),
Ring: ring,
Keys: kp,
}
return m.applyRingInfo(ringID, ri)
return m.applyRingInfo(ring, ri)
}
}
// RemoveWireguardKeys deletes wgN.key and wgN.pub from
// the machine's config directory
func (m *Machine) RemoveWireguardKeys(ringID rings.RingID) error {
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
return err
}
func (m *Machine) RemoveWireguardKeys(ring int) error {
var err error
keyFile, pubFile, _ := ring.Files()
err = m.RemoveFile(pubFile)
err = m.RemoveFile("wg%v.pub", ring)
switch {
case os.IsNotExist(err):
// ignore
@@ -99,7 +86,7 @@ func (m *Machine) RemoveWireguardKeys(ringID rings.RingID) error {
return err
}
err = m.RemoveFile(keyFile)
err = m.RemoveFile("wg%v.key", ring)
if os.IsNotExist(err) {
// ignore
err = nil
@@ -109,13 +96,8 @@ func (m *Machine) RemoveWireguardKeys(ringID rings.RingID) error {
}
// GetWireguardConfig reads a wgN.conf file
func (m *Machine) GetWireguardConfig(ringID rings.RingID) (*wireguard.Config, error) {
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
return nil, err
}
data, err := m.ReadFile(ring.ConfFile())
func (m *Machine) GetWireguardConfig(ring int) (*wireguard.Config, error) {
data, err := m.ReadFile("wg%v.conf", ring)
if err != nil {
return nil, err
}
@@ -124,7 +106,7 @@ func (m *Machine) GetWireguardConfig(ringID rings.RingID) (*wireguard.Config, er
return wireguard.NewConfigFromReader(r)
}
func (m *Machine) tryApplyWireguardConfig(ring rings.RingID) error {
func (m *Machine) tryApplyWireguardConfig(ring int) error {
wg, err := m.GetWireguardConfig(ring)
switch {
case os.IsNotExist(err):
@@ -136,15 +118,15 @@ func (m *Machine) tryApplyWireguardConfig(ring rings.RingID) error {
}
}
func (m *Machine) applyWireguardConfigNode(ring rings.RingID, wg *wireguard.Config) error {
func (m *Machine) applyWireguardConfigNode(ring int, wg *wireguard.Config) error {
addr := wg.GetAddress()
if !core.IsZero(addr) {
regionID, zoneID, nodeID, ok := Rings[ring].Decode(addr)
zoneID, nodeID, ok := Rings[ring].Decode(addr)
if !ok {
return fmt.Errorf("%s: invalid address", addr)
}
if err := m.applyZoneNodeID(regionID, zoneID, nodeID); err != nil {
if err := m.applyZoneNodeID(zoneID, nodeID); err != nil {
return core.Wrap(err, "%s: invalid address", addr)
}
}
@@ -156,7 +138,7 @@ func (m *Machine) applyWireguardConfigNode(ring rings.RingID, wg *wireguard.Conf
return nil
}
func (m *Machine) applyWireguardConfig(ring rings.RingID, wg *wireguard.Config) error {
func (m *Machine) applyWireguardConfig(ring int, wg *wireguard.Config) error {
if err := m.applyWireguardConfigNode(ring, wg); err != nil {
return err
}
@@ -170,7 +152,7 @@ func (m *Machine) applyWireguardConfig(ring rings.RingID, wg *wireguard.Config)
WithField("subsystem", "wireguard").
WithField("node", m.Name).
WithField("peer", peer.Endpoint.Host).
WithField("ring", MustWireguardInterfaceID(ring)).
WithField("ring", ring).
Print("ignoring unknown endpoint")
case err != nil:
return core.Wrap(err, "peer")
@@ -180,9 +162,9 @@ func (m *Machine) applyWireguardConfig(ring rings.RingID, wg *wireguard.Config)
return nil
}
func (m *Machine) getRingInfo(ring rings.RingID) (*RingInfo, bool) {
func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) {
for _, ri := range m.Rings {
if ri.RingID() == ring {
if ri.Ring == ring {
return ri, ri.Enabled
}
}
@@ -190,13 +172,13 @@ func (m *Machine) getRingInfo(ring rings.RingID) (*RingInfo, bool) {
return nil, false
}
func (m *Machine) applyRingInfo(ring rings.RingID, new *RingInfo) error {
func (m *Machine) applyRingInfo(ring int, new *RingInfo) error {
cur, _ := m.getRingInfo(ring)
if cur == nil {
// first, append
m.debug().
WithField("node", m.Name).
WithField("ring", MustWireguardInterfaceID(ring)).
WithField("ring", ring).
Print("found")
m.Rings = append(m.Rings, new)
return nil
@@ -206,11 +188,9 @@ func (m *Machine) applyRingInfo(ring rings.RingID, new *RingInfo) error {
return cur.Merge(new)
}
func (m *Machine) applyWireguardInterfaceConfig(ring rings.RingID,
data wireguard.InterfaceConfig) error {
//
func (m *Machine) applyWireguardInterfaceConfig(ring int, data wireguard.InterfaceConfig) error {
ri := &RingInfo{
Ring: MustWireguardInterfaceID(ring),
Ring: ring,
Enabled: true,
Keys: wireguard.KeyPair{
PrivateKey: data.PrivateKey,
@@ -220,9 +200,7 @@ func (m *Machine) applyWireguardInterfaceConfig(ring rings.RingID,
return m.applyRingInfo(ring, ri)
}
func (m *Machine) applyWireguardPeerConfig(ring rings.RingID,
pc wireguard.PeerConfig) error {
//
func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) error {
peer, found := m.getPeerByName(pc.Endpoint.Name())
switch {
case !found:
@@ -234,7 +212,7 @@ func (m *Machine) applyWireguardPeerConfig(ring rings.RingID,
default:
// apply RingInfo
ri := &RingInfo{
Ring: MustWireguardInterfaceID(ring),
Ring: ring,
Enabled: true,
Keys: wireguard.KeyPair{
PublicKey: pc.PublicKey,
@@ -245,29 +223,21 @@ func (m *Machine) applyWireguardPeerConfig(ring rings.RingID,
}
}
func (m *Machine) applyZoneNodeID(regionID rings.RegionID,
zoneID rings.ZoneID, nodeID rings.NodeID) error {
//
func (m *Machine) applyZoneNodeID(zoneID ZoneID, nodeID NodeID) error {
switch {
case !regionID.Valid():
return fmt.Errorf("invalid %s", "regionID")
case !zoneID.Valid():
case zoneID == 0:
return fmt.Errorf("invalid %s", "zoneID")
case !nodeID.Valid():
case nodeID == 0:
return fmt.Errorf("invalid %s", "nodeID")
case m.ID != nodeID:
return fmt.Errorf("invalid %s: %v ≠ %v", "nodeID", m.ID, nodeID)
return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.ID, nodeID)
case m.zone.ID != 0 && m.zone.ID != zoneID:
return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.zone.ID, zoneID)
case m.Region() != regionID:
return fmt.Errorf("invalid %s: %v ≠ %v", "regionID", m.Region(), regionID)
default:
if m.zone.ID == 0 {
m.zone.ID = zoneID
}
return nil
case m.zone.ID == 0:
m.zone.ID = zoneID
}
return nil
}
func (m *Machine) setRingDefaults(ri *RingInfo) error {
@@ -289,13 +259,8 @@ func (m *Machine) setRingDefaults(ri *RingInfo) error {
// RemoveWireguardConfig deletes wgN.conf from the machine's
// config directory.
func (m *Machine) RemoveWireguardConfig(ringID rings.RingID) error {
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
return err
}
err = m.RemoveFile(ring.ConfFile())
func (m *Machine) RemoveWireguardConfig(ring int) error {
err := m.RemoveFile("wg%v.conf", ring)
if os.IsNotExist(err) {
err = nil
}
@@ -303,12 +268,7 @@ func (m *Machine) RemoveWireguardConfig(ringID rings.RingID) error {
return err
}
func (m *Machine) createRingInfo(ringID rings.RingID, enabled bool) (*RingInfo, error) {
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
return nil, err
}
func (m *Machine) createRingInfo(ring int, enabled bool) (*RingInfo, error) {
keys, err := wireguard.NewKeyPair()
if err != nil {
return nil, err
@@ -320,7 +280,7 @@ func (m *Machine) createRingInfo(ringID rings.RingID, enabled bool) (*RingInfo,
Keys: keys,
}
err = m.applyRingInfo(ringID, ri)
err = m.applyRingInfo(ring, ri)
if err != nil {
return nil, err
}
+6 -8
View File
@@ -9,8 +9,6 @@ import (
"time"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
// LookupNetIP uses the DNS Resolver to get the public addresses associated
@@ -39,8 +37,8 @@ func (m *Machine) init() error {
return core.Wrap(err, m.Name)
}
for _, ring := range Rings {
if err := m.tryReadWireguardKeys(ring.ID); err != nil {
for i := 0; i < RingsCount; i++ {
if err := m.tryReadWireguardKeys(i); err != nil {
return core.Wrap(err, m.Name)
}
}
@@ -67,18 +65,18 @@ func (m *Machine) setID() error {
return err
}
m.ID = rings.NodeID(id)
m.ID = NodeID(id)
return nil
}
// scan is called once we know about all zones and machine names
func (m *Machine) scan(_ *ScanOptions) error {
for _, ring := range Rings {
if err := m.tryApplyWireguardConfig(ring.ID); err != nil {
for i := 0; i < RingsCount; i++ {
if err := m.tryApplyWireguardConfig(i); err != nil {
m.error(err).
WithField("subsystem", "wireguard").
WithField("node", m.Name).
WithField("ring", MustWireguardInterfaceID(ring.ID)).
WithField("ring", i).
Print()
return err
}
+2 -99
View File
@@ -3,8 +3,6 @@ package cluster
import (
"bytes"
"path/filepath"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var (
@@ -26,15 +24,8 @@ type Region struct {
zones []*Zone
Name string
ID rings.RegionID `json:",omitempty" yaml:",omitempty"`
Cluster *string `json:",omitempty" yaml:",omitempty"`
Regions []string `json:",omitempty" yaml:",omitempty"`
}
// IsPrimary indicates the region is primary and corresponds
// to a kubernetes cluster.
func (r *Region) IsPrimary() bool {
return r != nil && r.Cluster != nil
Cluster *string `json:",omitempty" yaml:",omitempty"`
Regions []string `json:",omitempty" yaml:",omitempty"`
}
// ForEachRegion calls a function for each Region of the cluster
@@ -101,8 +92,6 @@ func (m *Cluster) initRegions(_ *ScanOptions) error {
}
m.sortRegions()
m.scanRegionID()
m.computeZonesRegion()
return nil
}
@@ -221,92 +210,6 @@ func (m *Cluster) finishRegion(r *Region) {
r.Regions = sub
}
// revive:disable:cognitive-complexity
func (m *Cluster) scanRegionID() {
// revive:enable:cognitive-complexity
var max rings.RegionID
var missing bool
// check IDs
ids := make(map[rings.RegionID]bool)
fn := func(r *Region) bool {
var term bool
switch {
case !r.IsPrimary():
// secondary, no ID.
r.ID = 0
case !r.ID.Valid():
// primary without ID
missing = true
case ids[r.ID]:
// duplicate
m.error(nil).WithField("region", r.Name).Print("duplicate ID")
missing = true
r.ID = 0
default:
ids[r.ID] = true
if r.ID > max {
max = r.ID
}
}
return term
}
m.ForEachRegion(fn)
if missing {
// assign missing IDs
fn := func(r *Region) bool {
var term bool
switch {
case !r.IsPrimary():
// ignore secondary
case r.ID.Valid():
// already has an ID
default:
r.ID = max + 1
max = r.ID
}
return term
}
m.ForEachRegion(fn)
}
}
func (m *Cluster) computeZonesRegion() {
fn := func(r *Region, z *Zone) {
if z.region != nil {
m.error(nil).
WithField("zone", z.Name).
WithField("region", []string{
z.region.Name,
r.Name,
}).
Print("zone in two regions")
} else {
z.region = r
}
}
m.ForEachRegion(func(r *Region) bool {
var term bool
if r.IsPrimary() {
r.ForEachZone(func(z *Zone) bool {
fn(r, z)
return term
})
}
return term
})
}
func (m *Cluster) getRegion(name string) (*Region, bool) {
for i := range m.Regions {
r := &m.Regions[i]
+108 -104
View File
@@ -4,86 +4,31 @@ import (
"fmt"
"io/fs"
"net/netip"
"strconv"
"git.jpi.io/amery/jpictl/pkg/rings"
"git.jpi.io/amery/jpictl/pkg/wireguard"
)
const (
// MaxZoneID indicates the highest ID allowed for a Zone
MaxZoneID = 0xf
// MaxNodeID indicates the highest Machine ID allowed within a Zone
MaxNodeID = 0xff - 1
// RingsCount indicates how many wireguard rings we have
RingsCount = 2
// RingZeroPort is the port wireguard uses for ring0
RingZeroPort = 51800
// RingOnePort is the port wireguard uses for ring1
RingOnePort = 51810
)
// WireguardInterfaceID represents the number in the `wg%v`
// interface name.
type WireguardInterfaceID uint
// AsWireguardInterfaceID returns the [WireguardInterfaceID] for
// a valid [rings.RingID].
func AsWireguardInterfaceID(ring rings.RingID) (WireguardInterfaceID, error) {
switch ring {
case rings.RingZeroID:
return 0, nil
case rings.RingOneID:
return 1, nil
default:
return 0, ErrInvalidRing(ring)
}
}
// MustWireguardInterfaceID returns the [WireguardInterfaceID] for
// a valid [rings.RingID], and panics if it's not.
func MustWireguardInterfaceID(ring rings.RingID) WireguardInterfaceID {
id, err := AsWireguardInterfaceID(ring)
if err != nil {
panic(err)
}
return id
}
// RingID tells the [rings.RingID] of the [WireguardInterfaceID].
func (wi WireguardInterfaceID) RingID() rings.RingID {
return rings.RingID(wi + 1)
}
// PubFile returns "wgN.pub"
func (wi WireguardInterfaceID) PubFile() string {
return fmt.Sprintf("wg%v.pub", wi)
}
// KeyFile returns "wgN.key"
func (wi WireguardInterfaceID) KeyFile() string {
return fmt.Sprintf("wg%v.key", wi)
}
// ConfFile returns "wgN.conf"
func (wi WireguardInterfaceID) ConfFile() string {
return fmt.Sprintf("wg%v.conf", wi)
}
// Files returns all wgN.ext file names.
func (wi WireguardInterfaceID) Files() (keyFile, pubFile, confFile string) {
prefix := "wg" + strconv.Itoa(int(wi))
return prefix + ".key", prefix + ".pub", prefix + ".conf"
}
// RingInfo contains represents the Wireguard endpoint details
// for a Machine on a particular ring
type RingInfo struct {
Ring WireguardInterfaceID
Ring int
Enabled bool
Keys wireguard.KeyPair
}
// RingID returns the [rings.RingID] for this [RingInfo].
func (ri *RingInfo) RingID() rings.RingID {
return rings.RingID(ri.Ring + 1)
}
// Merge attempts to combine two RingInfo structs
func (ri *RingInfo) Merge(alter *RingInfo) error {
switch {
@@ -109,7 +54,7 @@ func (ri *RingInfo) unsafeMerge(alter *RingInfo) error {
ri.Enabled = true
}
// fill the gaps on our key pair
// fill the gaps on our keypair
if ri.Keys.PrivateKey.IsZero() {
ri.Keys.PrivateKey = alter.Keys.PrivateKey
}
@@ -134,34 +79,108 @@ func canMergeKeyPairs(p1, p2 wireguard.KeyPair) bool {
// RingAddressEncoder provides encoder/decoder access for a particular
// Wireguard ring
type RingAddressEncoder struct {
ID rings.RingID
ID int
Port uint16
Encode func(rings.RegionID, rings.ZoneID, rings.NodeID) (netip.Addr, error)
Decode func(addr netip.Addr) (rings.RegionID, rings.ZoneID, rings.NodeID, bool)
Encode func(zoneID ZoneID, nodeID NodeID) (netip.Addr, bool)
Decode func(addr netip.Addr) (zoneID ZoneID, nodeID NodeID, ok bool)
}
var (
// RingZero is a wg0 address encoder/decoder
RingZero = RingAddressEncoder{
ID: rings.RingZeroID,
ID: 0,
Port: RingZeroPort,
Decode: rings.DecodeRingZeroAddress,
Encode: rings.RingZeroAddress,
Decode: ParseRingZeroAddress,
Encode: RingZeroAddress,
}
// RingOne is a wg1 address encoder/decoder
RingOne = RingAddressEncoder{
ID: rings.RingOneID,
ID: 1,
Port: RingOnePort,
Decode: rings.DecodeRingOneAddress,
Encode: rings.RingOneAddress,
Decode: ParseRingOneAddress,
Encode: RingOneAddress,
}
// Rings provides indexed access to the ring address encoders
Rings = []RingAddressEncoder{
Rings = [RingsCount]RingAddressEncoder{
RingZero,
RingOne,
}
)
// ValidZoneID checks if the given zoneID is a valid 4 bit zone number.
//
// 0 is reserved, and only allowed when composing CIDRs.
func ValidZoneID(zoneID ZoneID) bool {
switch {
case zoneID < 0 || zoneID > MaxZoneID:
return false
default:
return true
}
}
// ValidNodeID checks if the given nodeID is a valid 8 bit number.
// nodeID is unique within a Zone.
// 0 is reserved, and only allowed when composing CIDRs.
func ValidNodeID(nodeID NodeID) bool {
switch {
case nodeID < 0 || nodeID > MaxNodeID:
return false
default:
return true
}
}
// ParseRingZeroAddress extracts zone and node ID from a wg0 [netip.Addr]
// wg0 addresses are of the form `10.0.{{zoneID}}.{{nodeID}}`
func ParseRingZeroAddress(addr netip.Addr) (zoneID ZoneID, nodeID NodeID, ok bool) {
if addr.IsValid() {
a4 := addr.As4()
if a4[0] == 10 && a4[1] == 0 {
return ZoneID(a4[2]), NodeID(a4[3]), true
}
}
return 0, 0, false
}
// RingZeroAddress returns a wg0 IP address
func RingZeroAddress(zoneID ZoneID, nodeID NodeID) (netip.Addr, bool) {
switch {
case !ValidZoneID(zoneID) || !ValidNodeID(nodeID):
return netip.Addr{}, false
default:
a4 := [4]uint8{10, 0, uint8(zoneID), uint8(nodeID)}
return netip.AddrFrom4(a4), true
}
}
// ParseRingOneAddress extracts zone and node ID from a wg1 [netip.Addr]
// wg1 addresses are of the form `10.{{zoneID << 4}}.{{nodeID}}`
func ParseRingOneAddress(addr netip.Addr) (zoneID ZoneID, nodeID NodeID, ok bool) {
if addr.IsValid() {
a4 := addr.As4()
if a4[0] == 10 && a4[2] == 0 {
zoneID = ZoneID(a4[1] >> 4)
nodeID = NodeID(a4[3])
return zoneID, nodeID, true
}
}
return 0, 0, false
}
// RingOneAddress returns a wg1 IP address
func RingOneAddress(zoneID ZoneID, nodeID NodeID) (netip.Addr, bool) {
switch {
case !ValidZoneID(zoneID) || !ValidNodeID(nodeID):
return netip.Addr{}, false
default:
a4 := [4]uint8{10, uint8(zoneID << 4), 0, uint8(nodeID)}
return netip.AddrFrom4(a4), true
}
}
var (
_ MachineIterator = (*Ring)(nil)
_ ZoneIterator = (*Ring)(nil)
@@ -184,15 +203,14 @@ func (r *Ring) AddPeer(p *Machine) bool {
nodeID := p.ID
zoneID := p.Zone()
regionID := p.Region()
addr, _ := r.Encode(regionID, zoneID, nodeID)
addr, _ := r.Encode(zoneID, nodeID)
rp := &RingPeer{
Node: p,
Address: addr,
PrivateKey: ri.Keys.PrivateKey,
PeerConfig: wireguard.PeerConfig{
Name: fmt.Sprintf("%s-%v", p.Name, ri.Ring),
Name: fmt.Sprintf("%s-%v", p.Name, r.ID),
PublicKey: ri.Keys.PublicKey,
Endpoint: wireguard.EndpointAddress{
Host: p.FullName(),
@@ -215,27 +233,27 @@ func (r *Ring) AddPeer(p *Machine) bool {
}
func (r *Ring) setRingZeroAllowedIPs(rp *RingPeer) {
regionID, zoneID, _, _ := r.Decode(rp.Address)
zoneID, _, _ := r.Decode(rp.Address)
// everyone on ring0 is a gateway to ring1
subnet, _ := rings.RingOnePrefix(regionID, zoneID)
rp.AllowSubnet(subnet)
addr, _ := RingOneAddress(zoneID, 0)
rp.AllowCIDR(addr, 12)
// peer
rp.AllowCIDR(rp.Address, 32)
}
func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) {
regionID, zoneID, _, _ := r.Decode(rp.Address)
zoneID, _, _ := r.Decode(rp.Address)
// peer
rp.AllowCIDR(rp.Address, 32)
// ring1 gateways connect to all other ring1 networks
r.ForEachZone(func(z *Zone) bool {
if !z.Is(regionID, zoneID) {
subnet := z.RingOnePrefix()
rp.AllowSubnet(subnet)
if z.ID != zoneID {
addr, _ := r.Encode(z.ID, 0)
rp.AllowCIDR(addr, 12)
}
return false
})
@@ -244,7 +262,7 @@ func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) {
r.ForEachZone(func(z *Zone) bool {
z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() {
addr, _ := p.RingZeroAddress()
addr, _ := RingZeroAddress(z.ID, p.ID)
rp.AllowCIDR(addr, 32)
}
return false
@@ -311,29 +329,15 @@ type RingPeer struct {
// AllowCIDR allows an IP range via this peer
func (rp *RingPeer) AllowCIDR(addr netip.Addr, bits int) {
rp.AllowSubnet(netip.PrefixFrom(addr, bits))
}
// AllowSubnet allows an IP range via this peer
func (rp *RingPeer) AllowSubnet(subnet netip.Prefix) {
rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, subnet)
cidr := netip.PrefixFrom(addr, bits)
rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, cidr)
}
// NewRing composes a new Ring for Wireguard setup
func NewRing(z ZoneIterator, m MachineIterator, ringID rings.RingID) (*Ring, error) {
var r *Ring
for _, ring := range Rings {
if ringID == ring.ID {
r = &Ring{
RingAddressEncoder: ring,
ZoneIterator: z,
}
break
}
}
if r == nil {
return nil, ErrInvalidRing(ringID)
func NewRing(z ZoneIterator, m MachineIterator, ring int) (*Ring, error) {
r := &Ring{
RingAddressEncoder: Rings[ring],
ZoneIterator: z,
}
m.ForEachMachine(func(p *Machine) bool {
+3 -3
View File
@@ -35,13 +35,13 @@ func (m *Cluster) SyncMkdirAll() error {
func (m *Cluster) SyncAllWireguard() error {
var err error
for _, ring := range Rings {
err = m.WriteWireguardKeys(ring.ID)
for ring := 0; ring < RingsCount; ring++ {
err = m.WriteWireguardKeys(ring)
if err != nil {
return err
}
err = m.SyncWireguardConfig(ring.ID)
err = m.SyncWireguardConfig(ring)
if err != nil {
return err
}
+25
View File
@@ -0,0 +1,25 @@
package cluster
import "strconv"
type (
// ZoneID is the identifier of a [Zone]
ZoneID int
// NodeID is the identifier of a [Machine]
NodeID int
)
func (z ZoneID) String() string {
return idString(int(z))
}
func (n NodeID) String() string {
return idString(int(n))
}
func idString(v int) string {
if v > 0 {
return strconv.Itoa(v)
}
return ""
}
+36 -44
View File
@@ -3,8 +3,6 @@ package cluster
import (
"io/fs"
"os"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var (
@@ -28,22 +26,22 @@ var (
// A WireguardConfigPruner deletes wgN.conf on all machines under
// its scope with the specified ring disabled
type WireguardConfigPruner interface {
PruneWireguardConfig(ring rings.RingID) error
PruneWireguardConfig(ring int) error
}
// PruneWireguardConfig removes wgN.conf files of machines with
// the corresponding ring disabled on all zones
func (m *Cluster) PruneWireguardConfig(ring rings.RingID) error {
func (m *Cluster) PruneWireguardConfig(ring int) error {
return pruneWireguardConfig(m, ring)
}
// PruneWireguardConfig removes wgN.conf files of machines with
// the corresponding ring disabled.
func (z *Zone) PruneWireguardConfig(ring rings.RingID) error {
func (z *Zone) PruneWireguardConfig(ring int) error {
return pruneWireguardConfig(z, ring)
}
func pruneWireguardConfig(m MachineIterator, ring rings.RingID) error {
func pruneWireguardConfig(m MachineIterator, ring int) error {
var err error
m.ForEachMachine(func(p *Machine) bool {
@@ -61,7 +59,7 @@ func pruneWireguardConfig(m MachineIterator, ring rings.RingID) error {
// PruneWireguardConfig deletes the wgN.conf file if its
// presence on the ring is disabled
func (m *Machine) PruneWireguardConfig(ring rings.RingID) error {
func (m *Machine) PruneWireguardConfig(ring int) error {
_, ok := m.getRingInfo(ring)
if !ok {
return m.RemoveWireguardConfig(ring)
@@ -73,16 +71,16 @@ func (m *Machine) PruneWireguardConfig(ring rings.RingID) error {
// A WireguardConfigWriter rewrites all wgN.conf on all machines under
// its scope attached to that ring
type WireguardConfigWriter interface {
WriteWireguardConfig(ring rings.RingID) error
WriteWireguardConfig(ring int) error
}
// WriteWireguardConfig rewrites all wgN.conf on all machines
// attached to that ring
func (m *Cluster) WriteWireguardConfig(ring rings.RingID) error {
func (m *Cluster) WriteWireguardConfig(ring int) error {
switch ring {
case rings.RingZeroID:
case 0:
return writeWireguardConfig(m, m, ring)
case rings.RingOneID:
case 1:
var err error
m.ForEachZone(func(z *Zone) bool {
err = writeWireguardConfig(m, z, ring)
@@ -90,24 +88,24 @@ func (m *Cluster) WriteWireguardConfig(ring rings.RingID) error {
})
return err
default:
return ErrInvalidRing(ring)
return fs.ErrInvalid
}
}
// WriteWireguardConfig rewrites all wgN.conf on all machines
// on the Zone attached to that ring
func (z *Zone) WriteWireguardConfig(ring rings.RingID) error {
func (z *Zone) WriteWireguardConfig(ring int) error {
switch ring {
case rings.RingZeroID:
case 0:
return writeWireguardConfig(z.zones, z.zones, ring)
case rings.RingOneID:
case 1:
return writeWireguardConfig(z.zones, z, ring)
default:
return ErrInvalidRing(ring)
return fs.ErrInvalid
}
}
func writeWireguardConfig(z ZoneIterator, m MachineIterator, ring rings.RingID) error {
func writeWireguardConfig(z ZoneIterator, m MachineIterator, ring int) error {
r, err := NewRing(z, m, ring)
if err != nil {
return err
@@ -123,7 +121,7 @@ func writeWireguardConfig(z ZoneIterator, m MachineIterator, ring rings.RingID)
// WriteWireguardConfig rewrites the wgN.conf file of this Machine
// if enabled
func (m *Machine) WriteWireguardConfig(ring rings.RingID) error {
func (m *Machine) WriteWireguardConfig(ring int) error {
r, err := NewRing(m.zone.zones, m.zone, ring)
if err != nil {
return err
@@ -133,17 +131,12 @@ func (m *Machine) WriteWireguardConfig(ring rings.RingID) error {
}
func (m *Machine) writeWireguardRingConfig(r *Ring) error {
ring, err := AsWireguardInterfaceID(r.ID)
if err != nil {
return err
}
wg, err := r.ExportConfig(m)
if err != nil {
return nil
}
f, err := m.CreateTruncFile(ring.ConfFile())
f, err := m.CreateTruncFile("wg%v.conf", r.ID)
if err != nil {
return err
}
@@ -156,16 +149,16 @@ func (m *Machine) writeWireguardRingConfig(r *Ring) error {
// A WireguardConfigSyncer updates all wgN.conf on all machines under
// its scope reflecting the state of the ring
type WireguardConfigSyncer interface {
SyncWireguardConfig(ring rings.RingID) error
SyncWireguardConfig(ring int) error
}
// SyncWireguardConfig updates all wgN.conf files for the specified
// ring
func (m *Cluster) SyncWireguardConfig(ring rings.RingID) error {
func (m *Cluster) SyncWireguardConfig(ring int) error {
switch ring {
case rings.RingZeroID:
case 0:
return syncWireguardConfig(m, m, ring)
case rings.RingOneID:
case 1:
var err error
m.ForEachZone(func(z *Zone) bool {
err = syncWireguardConfig(m, z, ring)
@@ -173,24 +166,24 @@ func (m *Cluster) SyncWireguardConfig(ring rings.RingID) error {
})
return err
default:
return ErrInvalidRing(ring)
return fs.ErrInvalid
}
}
// SyncWireguardConfig updates all wgN.conf files for the specified
// ring
func (z *Zone) SyncWireguardConfig(ring rings.RingID) error {
func (z *Zone) SyncWireguardConfig(ring int) error {
switch ring {
case rings.RingZeroID:
case 0:
return syncWireguardConfig(z.zones, z.zones, ring)
case rings.RingOneID:
case 1:
return syncWireguardConfig(z.zones, z, ring)
default:
return ErrInvalidRing(ring)
return fs.ErrInvalid
}
}
func syncWireguardConfig(z ZoneIterator, m MachineIterator, ring rings.RingID) error {
func syncWireguardConfig(z ZoneIterator, m MachineIterator, ring int) error {
r, err := NewRing(z, m, ring)
if err != nil {
return err
@@ -210,27 +203,27 @@ func syncWireguardConfig(z ZoneIterator, m MachineIterator, ring rings.RingID) e
// SyncWireguardConfig updates all wgN.conf files for the specified
// ring
func (m *Machine) SyncWireguardConfig(ring rings.RingID) error {
func (m *Machine) SyncWireguardConfig(ring int) error {
return m.zone.SyncWireguardConfig(ring)
}
// A WireguardKeysWriter writes the Wireguard Keys for all machines
// under its scope for the specified ring
type WireguardKeysWriter interface {
WriteWireguardKeys(ring rings.RingID) error
WriteWireguardKeys(ring int) error
}
// WriteWireguardKeys rewrites all wgN.{key,pub} files
func (m *Cluster) WriteWireguardKeys(ring rings.RingID) error {
func (m *Cluster) WriteWireguardKeys(ring int) error {
return writeWireguardKeys(m, ring)
}
// WriteWireguardKeys rewrites all wgN.{key,pub} files on this zone
func (z *Zone) WriteWireguardKeys(ring rings.RingID) error {
func (z *Zone) WriteWireguardKeys(ring int) error {
return writeWireguardKeys(z, ring)
}
func writeWireguardKeys(m MachineIterator, ring rings.RingID) error {
func writeWireguardKeys(m MachineIterator, ring int) error {
var err error
m.ForEachMachine(func(p *Machine) bool {
@@ -247,12 +240,12 @@ func writeWireguardKeys(m MachineIterator, ring rings.RingID) error {
}
// WriteWireguardKeys writes the wgN.key/wgN.pub files
func (m *Machine) WriteWireguardKeys(ringID rings.RingID) error {
func (m *Machine) WriteWireguardKeys(ring int) error {
var err error
var key, pub string
var ri *RingInfo
ri, _ = m.getRingInfo(ringID)
ri, _ = m.getRingInfo(ring)
if ri != nil {
key = ri.Keys.PrivateKey.String()
pub = ri.Keys.PublicKey.String()
@@ -265,13 +258,12 @@ func (m *Machine) WriteWireguardKeys(ringID rings.RingID) error {
pub = ri.Keys.PrivateKey.Public().String()
}
keyFile, pubFile, _ := ri.Ring.Files()
err = m.WriteStringFile(key+"\n", keyFile)
err = m.WriteStringFile(key+"\n", "wg%v.key", ring)
if err != nil {
return err
}
err = m.WriteStringFile(pub+"\n", pubFile)
err = m.WriteStringFile(pub+"\n", "wg%v.pub", ring)
if err != nil {
return err
}
+4 -42
View File
@@ -2,8 +2,6 @@ package cluster
import (
"io/fs"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var (
@@ -19,10 +17,9 @@ type ZoneIterator interface {
// affinity.
type Zone struct {
zones *Cluster
region *Region
logger `json:"-" yaml:"-"`
ID rings.ZoneID
ID ZoneID
Name string
Regions []string `json:",omitempty" yaml:",omitempty"`
@@ -34,7 +31,7 @@ func (z *Zone) String() string {
}
// SetGateway configures a machine to be the zone's ring0 gateway
func (z *Zone) SetGateway(gatewayID rings.NodeID, enabled bool) error {
func (z *Zone) SetGateway(gatewayID NodeID, enabled bool) error {
var err error
var found bool
@@ -59,8 +56,8 @@ func (z *Zone) SetGateway(gatewayID rings.NodeID, enabled bool) error {
}
// GatewayIDs returns the list of IDs of machines that act as ring0 gateways
func (z *Zone) GatewayIDs() ([]rings.NodeID, int) {
var out []rings.NodeID
func (z *Zone) GatewayIDs() ([]NodeID, int) {
var out []NodeID
z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() {
out = append(out, p.ID)
@@ -70,38 +67,3 @@ func (z *Zone) GatewayIDs() ([]rings.NodeID, int) {
return out, len(out)
}
// RegionID returns the primary [Region] of a [Zone].
func (z *Zone) RegionID() rings.RegionID {
if z != nil && z.region != nil {
return z.region.ID
}
return 0
}
// Is checks if the given [rings.RegionID] and [rings.ZoneID] match
// the [Zone].
func (z *Zone) Is(regionID rings.RegionID, zoneID rings.ZoneID) bool {
switch {
case z.ID != zoneID:
return false
case z.RegionID() != regionID:
return false
default:
return true
}
}
// Eq checks if two [Zone]s are the same.
func (z *Zone) Eq(z2 *Zone) bool {
switch {
case z == nil, z2 == nil:
return false
case z.ID != z2.ID:
return false
case z.RegionID() != z2.RegionID():
return false
default:
return true
}
}
-122
View File
@@ -1,122 +0,0 @@
package rings
import (
"net/netip"
)
// DecodeAddress extracts ring address fields from a given 10.0.0.0/8
// address.
//
// revive:disable:function-result-limit
func DecodeAddress[T ~uint | NodeID](addr netip.Addr) (RingID, RegionID, ZoneID, T) {
// revive:enable:function-result-limit
if addr.IsValid() {
if addr.Is4In6() {
addr = addr.Unmap()
}
if addr.Is4() {
a4 := addr.As4()
return unsafeDecodeAddress[T](a4[0], a4[1], a4[2], a4[3])
}
}
return UnspecifiedRingID, 0, 0, 0
}
// revive:disable:function-result-limit
func unsafeDecodeAddress[T ~uint | NodeID](a, b, c, d byte) (RingID, RegionID, ZoneID, T) {
// revive:enable:function-result-limit
switch {
case a != 10:
return UnspecifiedRingID, 0, 0, 0
case b == 0x00:
// 10.00.RZ.dd
k := RingZeroID
r := RegionID(c >> 4)
z := ZoneID(c & 0xf)
n := T(d)
return k, r, z, n
case b&0xf0 != 0:
// 10.Rb.cc.dd
k := RingThreeID
r := RegionID(b >> 4)
n2 := T(b & 0x0f)
n1 := T(c)
n0 := T(d)
n := n0 + n1<<8 + n2<<16
return k, r, 0, n
case c&0xf0 != 0:
// 10.0R.Zc.dd
k := RingOneID
r := RegionID(b)
z := ZoneID(c >> 4)
n1 := T(c & 0x0f)
n0 := T(d)
n := n0 + n1<<8
return k, r, z, n
default:
// 10.0R.0c.dd
k := RingTwoID
r := RegionID(b)
n1 := T(c & 0x0f)
n0 := T(d)
n := n0 + n1<<8
return k, r, 0, n
}
}
// DecodeRingZeroAddress attempts to extract region, zone and node identifiers
// from a given ring 0 address.
//
// revive:disable:function-result-limit
func DecodeRingZeroAddress(addr netip.Addr) (RegionID, ZoneID, NodeID, bool) {
// revive:enable:function-result-limit
k, r, z, n := DecodeAddress[NodeID](addr)
if k == RingZeroID {
return r, z, n, true
}
return 0, 0, 0, false
}
// DecodeRingOneAddress attempts to extract region, zone and node identifiers
// from a given ring 1 address.
//
// revive:disable:function-result-limit
func DecodeRingOneAddress(addr netip.Addr) (RegionID, ZoneID, NodeID, bool) {
// revive:enable:function-result-limit
k, r, z, n := DecodeAddress[NodeID](addr)
if k == RingOneID {
return r, z, n, true
}
return 0, 0, 0, false
}
// DecodeRingTwoAddress attempts to extract region and unique identifier for
// a kubernetes service from a given ring 2 address.
func DecodeRingTwoAddress(addr netip.Addr) (RegionID, uint, bool) {
k, r, _, n := DecodeAddress[uint](addr)
if k == RingTwoID {
return r, n, true
}
return 0, 0, false
}
// DecodeRingThreeAddress attempts to extract region and unique identifier for
// a kubernetes pod from a given ring 3 address.
func DecodeRingThreeAddress(addr netip.Addr) (RegionID, uint, bool) {
k, r, _, n := DecodeAddress[uint](addr)
if k == RingThreeID {
return r, n, true
}
return 0, 0, false
}
-53
View File
@@ -1,53 +0,0 @@
package rings
import (
"fmt"
"net/netip"
"testing"
)
func TestDecodeRingZeroAddress(t *testing.T) {
RZNDecodeTest(t, "DecodeRingZeroAddress", DecodeRingZeroAddress, []RZNDecodeTestCase{
{1, 1, 50, MustParseAddr("10.0.17.50"), true},
{1, 2, 50, MustParseAddr("10.0.18.50"), true},
{2, 3, 1, MustParseAddr("10.0.35.1"), true},
})
}
func TesDecodetRingOneAddress(t *testing.T) {
RZNDecodeTest(t, "DecodeRingOneAddress", DecodeRingOneAddress, []RZNDecodeTestCase{
{1, 1, 50, MustParseAddr("10.1.16.50"), true},
{1, 2, 50, MustParseAddr("10.1.32.50"), true},
{2, 3, 300, MustParseAddr("10.2.49.44"), true},
})
}
type RZNDecodeTestCase struct {
region RegionID
zone ZoneID
node NodeID
addr netip.Addr
ok bool
}
func RZNDecodeTest(t *testing.T,
fnName string, fn func(netip.Addr) (RegionID, ZoneID, NodeID, bool),
cases []RZNDecodeTestCase) {
//
for i, tc := range cases {
s := fmt.Sprintf("%s(%q)", fnName, tc.addr)
r, z, n, ok := fn(tc.addr)
switch {
case ok != tc.ok, r != tc.region, z != tc.zone, n != tc.node:
t.Errorf("ERROR: [%v/%v]: %s → %v %v %v %v (expected %v %v %v %v)",
i, len(cases), s,
r, z, n, ok,
tc.region, tc.zone, tc.node, tc.ok)
default:
t.Logf("[%v/%v]: %s → %v %v %v %v", i, len(cases), s,
r, z, n, ok)
}
}
}
+2 -58
View File
@@ -3,34 +3,21 @@
package rings
import (
"fmt"
"strconv"
"syscall"
"darvaza.org/core"
)
const (
// UnspecifiedRingID is the zero value of RingID and not considered
// valid.
UnspecifiedRingID RingID = iota
RingZeroID // RingZeroID is the RingID for RingZero (backbone)
RingOneID // RingOneID is the RingID for RingOne (local zone)
RingTwoID // RingTwoID is the RingID for RingTwo (region services)
RingThreeID // RingThreeID is the RingID for RingThree (region cluster pods)
// RingMax indicates the highest [Ring] identifier
RingMax = RingThreeID
// RegionMax indicates the highest number that can be used for a [RegionID].
RegionMax = (1 << 4) - 1
// ZoneMax indicates the highest number that can be used for a [ZoneID].
ZoneMax = (1 << 4) - 1
// NodeMax indicates the highest number that can be used for a [NodeID].
NodeMax = (1 << 12) - 2
NodeMax = (1 << 12) - 1
// NodeZeroMax indicates the highest number that can be used for a [NodeID]
// when its a gateway connected to Ring 0 (backbone).
NodeZeroMax = (1 << 8) - 2
NodeZeroMax = (1 << 8) - 1
// RingZeroBits indicates the size of the prefix on the ring 0 (backbone) network.
RingZeroBits = 16
@@ -44,41 +31,18 @@ const (
RingThreeBits = 12
)
// RingID identifies a Ring
type RingID int
// Valid tells a [RingID] is within the valid range.
func (n RingID) Valid() bool { return n > 0 && n <= RingMax }
func (n RingID) String() string {
return idString(n)
}
// A Ring identifies what ring an address belongs to
type Ring interface {
ID() RingID
}
// RegionID is the identifier of a region, valid between 1 and [RegionMax].
type RegionID int
// Valid tells a [RegionID] is within the valid range.
func (n RegionID) Valid() bool { return n > 0 && n <= RegionMax }
func (n RegionID) String() string {
return idString(n)
}
// ZoneID is the identifier of a zone within a region, valid between 1 and [ZoneMax].
type ZoneID int
// Valid tells a [ZoneID] is within the valid range.
func (n ZoneID) Valid() bool { return n > 0 && n <= ZoneMax }
func (n ZoneID) String() string {
return idString(n)
}
// NodeID is the identifier of a machine within a zone of a region, valid between
// 1 and [NodeMax], but between 1 and [NodeZeroMax] if it will be a zone gateway.
type NodeID int
@@ -89,28 +53,8 @@ func (n NodeID) Valid() bool { return n > 0 && n <= NodeMax }
// ValidZero tells a [NodeID] is within the valid range for a gateway.
func (n NodeID) ValidZero() bool { return n > 0 && n <= NodeZeroMax }
func (n NodeID) String() string {
return idString(n)
}
// ErrOutOfRange is an error indicating the value of a field
// is out of range.
func ErrOutOfRange[T ~int | ~uint32](value T, field string) error {
return core.Wrap(syscall.EINVAL, "%s out of range (%v)", field, value)
}
type intID interface {
~int
Valid() bool
}
func idString[T intID](p T) string {
switch {
case p == 0:
return "unspecified"
case p.Valid():
return strconv.Itoa(int(p))
default:
return fmt.Sprintf("invalid (%v)", int(p))
}
}