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 264 additions and 713 deletions
+1 -2
View File
@@ -15,8 +15,7 @@ TMPDIR ?= .tmp
REVIVE ?= $(GOBIN)/revive REVIVE ?= $(GOBIN)/revive
REVIVE_CONF ?= $(TOOLSDIR)/revive.toml REVIVE_CONF ?= $(TOOLSDIR)/revive.toml
REVIVE_RUN_ARGS ?= -config $(REVIVE_CONF) -formatter friendly REVIVE_RUN_ARGS ?= -config $(REVIVE_CONF) -formatter friendly
REVIVE_VERSION ?= v1.3.7 REVIVE_INSTALL_URL ?= github.com/mgechev/revive@master
REVIVE_INSTALL_URL ?= github.com/mgechev/revive@$(REVIVE_VERSION)
GO_INSTALL_URLS = \ GO_INSTALL_URLS = \
$(REVIVE_INSTALL_URL) \ $(REVIVE_INSTALL_URL) \
+1 -1
View File
@@ -4,7 +4,7 @@ go 1.19
require ( require (
asciigoat.org/ini v0.2.5 asciigoat.org/ini v0.2.5
darvaza.org/core v0.13.3 darvaza.org/core v0.13.1
darvaza.org/resolver v0.9.2 darvaza.org/resolver v0.9.2
darvaza.org/sidecar v0.4.0 darvaza.org/sidecar v0.4.0
darvaza.org/slog v0.5.7 darvaza.org/slog v0.5.7
+2 -2
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= 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 h1:rvFucut4wKYbsYc994yR3P0M08NqlsvZxr5G4QK82tw=
darvaza.org/cache/x/simplelru v0.1.8/go.mod h1:Mv1isOJTcXYK+aK0AvUe+/3KpRTXDsYga6rdTS/upNs= darvaza.org/cache/x/simplelru v0.1.8/go.mod h1:Mv1isOJTcXYK+aK0AvUe+/3KpRTXDsYga6rdTS/upNs=
darvaza.org/core v0.13.3 h1:DOsidY49WXsWiJulOIxDq578h/3ekgx0trWxbvgv5bc= darvaza.org/core v0.13.1 h1:ZoAfZ3OLnw+t28qMQQxXrDIkETmT2h5gAO6F1XuBpwg=
darvaza.org/core v0.13.3/go.mod h1:47Ydh67KnzjLNu1mzX3r2zpphbxQqEaihMsUq5GflQ4= 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 h1:sUX6LZ1eN5TzJW7L4m7HM+BvwBeWl8dYYDGVSe+AIhk=
darvaza.org/resolver v0.9.2/go.mod h1:XWqPhrxoOKNzRuSozOwmE1M6QVqQL28jEdxylnIO8Nw= darvaza.org/resolver v0.9.2/go.mod h1:XWqPhrxoOKNzRuSozOwmE1M6QVqQL28jEdxylnIO8Nw=
darvaza.org/sidecar v0.4.0 h1:wHghxzLsiT82WDBBUf34aTqtOvRBg4UbxVIJgKNXRVA= darvaza.org/sidecar v0.4.0 h1:wHghxzLsiT82WDBBUf34aTqtOvRBg4UbxVIJgKNXRVA=
-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 { m.ForEachZone(func(z *Zone) bool {
for _, p := range z.GetCephMonitors() { 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.Monitors = append(cfg.Global.Monitors, p.Name)
cfg.Global.MonitorsAddr = append(cfg.Global.MonitorsAddr, addr) cfg.Global.MonitorsAddr = append(cfg.Global.MonitorsAddr, addr)
+2 -2
View File
@@ -4,7 +4,6 @@ import (
"os" "os"
"darvaza.org/slog" "darvaza.org/slog"
"git.jpi.io/amery/jpictl/pkg/ceph" "git.jpi.io/amery/jpictl/pkg/ceph"
) )
@@ -15,7 +14,8 @@ type cephScanTODO struct {
func (todo *cephScanTODO) checkMachine(p *Machine) bool { func (todo *cephScanTODO) checkMachine(p *Machine) bool {
// on ceph all addresses are ring1 // 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 { if _, found := todo.names[p.Name]; found {
// found on the TODO by name // found on the TODO by name
+2 -4
View File
@@ -6,18 +6,16 @@ import (
"os" "os"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
func (m *Cluster) init(opts *ScanOptions) error { func (m *Cluster) init(opts *ScanOptions) error {
for _, fn := range []func(*ScanOptions) error{ for _, fn := range []func(*ScanOptions) error{
m.initZones, m.initZones,
m.initRegions,
m.scanZoneIDs, m.scanZoneIDs,
m.scanSort, m.scanSort,
m.scanGateways, m.scanGateways,
m.initCephMonitors, m.initCephMonitors,
m.initRegions,
} { } {
if err := fn(opts); err != nil { if err := fn(opts); err != nil {
return err return err
@@ -47,7 +45,7 @@ func (m *Cluster) initZones(opts *ScanOptions) error {
func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error { func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error {
var hasMissing bool var hasMissing bool
var lastMachineID rings.NodeID var lastMachineID NodeID
z.zones = m z.zones = m
z.logger = m z.logger = m
+2 -4
View File
@@ -7,8 +7,6 @@ import (
"strings" "strings"
"darvaza.org/core" "darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
const ( const (
@@ -25,9 +23,9 @@ func (m *Cluster) scan(opts *ScanOptions) error {
for _, fn := range []func(*ScanOptions) error{ for _, fn := range []func(*ScanOptions) error{
m.scanDirectory, m.scanDirectory,
m.scanMachines, m.scanMachines,
m.initRegions,
m.scanZoneIDs, m.scanZoneIDs,
m.scanSort, m.scanSort,
m.initRegions,
m.scanGateways, m.scanGateways,
m.scanCephMonitors, m.scanCephMonitors,
} { } {
@@ -116,7 +114,7 @@ func (m *Cluster) scanMachines(opts *ScanOptions) error {
func (m *Cluster) scanZoneIDs(_ *ScanOptions) error { func (m *Cluster) scanZoneIDs(_ *ScanOptions) error {
var hasMissing bool var hasMissing bool
var lastZoneID rings.ZoneID var lastZoneID ZoneID
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
switch { switch {
+16 -10
View File
@@ -6,8 +6,6 @@ import (
"io" "io"
"sort" "sort"
"strings" "strings"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
// Env is a shell environment factory for this cluster // Env is a shell environment factory for this cluster
@@ -37,8 +35,8 @@ func (m *Cluster) Env(export bool) (*Env, error) {
} }
// Zones returns the list of Zone IDs // Zones returns the list of Zone IDs
func (m *Env) Zones() []rings.ZoneID { func (m *Env) Zones() []ZoneID {
var zones []rings.ZoneID var zones []ZoneID
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
zones = append(zones, z.ID) zones = append(zones, z.ID)
@@ -53,7 +51,7 @@ func (m *Env) Regions() []string {
var regions []string var regions []string
m.ForEachRegion(func(r *Region) bool { m.ForEachRegion(func(r *Region) bool {
if r.IsPrimary() { if r.Cluster != nil {
regions = append(regions, r.Name) regions = append(regions, r.Name)
} }
@@ -163,10 +161,18 @@ func genEnvZoneNodes(z *Zone) string {
} }
func genEnvZoneRegion(z *Zone) string { func genEnvZoneRegion(z *Zone) string {
if z != nil && z.region != nil { var region string
return z.region.Name
} z.ForEachRegion(func(r *Region) bool {
return "" if r.Cluster != nil {
region = r.Name
return true
}
return false
})
return region
} }
func genEnvZoneCephMonNames(m Machines) string { func genEnvZoneCephMonNames(m Machines) string {
@@ -185,7 +191,7 @@ func genEnvZoneCephMonNames(m Machines) string {
func genEnvZoneCephMonIPs(m Machines) string { func genEnvZoneCephMonIPs(m Machines) string {
var buf strings.Builder var buf strings.Builder
m.ForEachMachine(func(p *Machine) bool { m.ForEachMachine(func(p *Machine) bool {
addr := p.RingOneAddress() addr, _ := RingOneAddress(p.Zone(), p.ID)
if buf.Len() > 0 { if buf.Len() > 0 {
_, _ = buf.WriteRune(' ') _, _ = buf.WriteRune(' ')
+1 -14
View File
@@ -1,13 +1,6 @@
package cluster package cluster
import ( import "errors"
"errors"
"io/fs"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var ( var (
// ErrInvalidName indicates the name isn't valid // ErrInvalidName indicates the name isn't valid
@@ -21,9 +14,3 @@ var (
// the intended purpose // the intended purpose
ErrInvalidNode = errors.New("invalid node") 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) { func (z *Zone) genHosts(out *hostsFile, p *Machine) {
var names []string var names []string
ip := p.RingOneAddress() ip, _ := RingOneAddress(p.zone.ID, p.ID)
names = append(names, p.Name) names = append(names, p.Name)
if p.CephMonitor { if p.CephMonitor {
names = append(names, fmt.Sprintf("%s-%s", p.zone.Name, "ceph")) names = append(names, fmt.Sprintf("%s-%s", p.zone.Name, "ceph"))
names = append(names, fmt.Sprintf("%s-%s", p.zone.Name, "k3s")) 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, "ceph")
names = append(names, "k3s") names = append(names, "k3s")
} }
@@ -94,7 +94,7 @@ func (z *Zone) genHosts(out *hostsFile, p *Machine) {
if p.IsGateway() { if p.IsGateway() {
var s string var s string
ip, _ = p.RingZeroAddress() ip, _ = RingZeroAddress(p.zone.ID, p.ID)
s = fmt.Sprintf("%s-%v", p.Name, 0) s = fmt.Sprintf("%s-%v", p.Name, 0)
entry = hostsEntry{ entry = hostsEntry{
+5 -12
View File
@@ -3,8 +3,6 @@ package cluster
import ( import (
"net/netip" "net/netip"
"strings" "strings"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
// revive:disable:line-length-limit // revive:disable:line-length-limit
@@ -14,7 +12,7 @@ type Machine struct {
zone *Zone zone *Zone
logger `json:"-" yaml:"-"` logger `json:"-" yaml:"-"`
ID rings.NodeID ID NodeID
Name string `json:"-" yaml:"-"` Name string `json:"-" yaml:"-"`
Inactive bool `json:"inactive,omitempty" yaml:"inactive,omitempty"` 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 // IsGateway tells if the Machine is a ring0 gateway
func (m *Machine) IsGateway() bool { func (m *Machine) IsGateway() bool {
_, ok := m.getRingInfo(rings.RingZeroID) _, ok := m.getRingInfo(0)
return ok return ok
} }
// SetGateway enables/disables a Machine ring0 integration // SetGateway enables/disables a Machine ring0 integration
func (m *Machine) SetGateway(enabled bool) error { func (m *Machine) SetGateway(enabled bool) error {
ri, found := m.getRingInfo(rings.RingZeroID) ri, found := m.getRingInfo(0)
switch { switch {
case !found && !enabled: case !found && !enabled:
return nil return nil
@@ -72,19 +70,14 @@ func (m *Machine) SetGateway(enabled bool) error {
} }
ri.Enabled = enabled ri.Enabled = enabled
return m.SyncWireguardConfig(rings.RingZeroID) return m.SyncWireguardConfig(0)
} }
// Zone indicates the [Zone] this machine belongs to // Zone indicates the [Zone] this machine belongs to
func (m *Machine) Zone() rings.ZoneID { func (m *Machine) Zone() ZoneID {
return m.zone.ID 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) { func (m *Machine) getPeerByName(name string) (*Machine, bool) {
return m.zone.zones.GetMachineByName(name) return m.zone.zones.GetMachineByName(name)
} }
+42 -82
View File
@@ -8,26 +8,18 @@ import (
"darvaza.org/core" "darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
"git.jpi.io/amery/jpictl/pkg/wireguard" "git.jpi.io/amery/jpictl/pkg/wireguard"
) )
// GetWireguardKeys reads a wgN.key/wgN.pub files // 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 ( var (
data []byte data []byte
err error
out wireguard.KeyPair out wireguard.KeyPair
) )
ring, err := AsWireguardInterfaceID(ringID) data, err = m.ReadFile("wg%v.key", ring)
if err != nil {
// invalid ring
return out, err
}
keyFile, pubFile, _ := ring.Files()
data, err = m.ReadFile(keyFile)
if err != nil { if err != nil {
// failed to read // failed to read
return out, err return out, err
@@ -36,11 +28,11 @@ func (m *Machine) GetWireguardKeys(ringID rings.RingID) (wireguard.KeyPair, erro
out.PrivateKey, err = wireguard.PrivateKeyFromBase64(string(data)) out.PrivateKey, err = wireguard.PrivateKeyFromBase64(string(data))
if err != nil { if err != nil {
// bad key // bad key
err = core.Wrap(err, keyFile) err = core.Wrap(err, "wg%v.key", ring)
return out, err return out, err
} }
data, err = m.ReadFile(pubFile) data, err = m.ReadFile("wg%v.pub", ring)
switch { switch {
case os.IsNotExist(err): case os.IsNotExist(err):
// no wgN.pub is fine // 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)) out.PublicKey, err = wireguard.PublicKeyFromBase64(string(data))
if err != nil { if err != nil {
// bad key // bad key
err = core.Wrap(err, pubFile) err = core.Wrap(err, "wg%v.pub", ring)
return out, err return out, err
} }
} }
@@ -61,8 +53,8 @@ func (m *Machine) GetWireguardKeys(ringID rings.RingID) (wireguard.KeyPair, erro
return out, err return out, err
} }
func (m *Machine) tryReadWireguardKeys(ringID rings.RingID) error { func (m *Machine) tryReadWireguardKeys(ring int) error {
kp, err := m.GetWireguardKeys(ringID) kp, err := m.GetWireguardKeys(ring)
switch { switch {
case os.IsNotExist(err): case os.IsNotExist(err):
// ignore // ignore
@@ -73,25 +65,20 @@ func (m *Machine) tryReadWireguardKeys(ringID rings.RingID) error {
default: default:
// import keys // import keys
ri := &RingInfo{ ri := &RingInfo{
Ring: MustWireguardInterfaceID(ringID), Ring: ring,
Keys: kp, Keys: kp,
} }
return m.applyRingInfo(ringID, ri) return m.applyRingInfo(ring, ri)
} }
} }
// RemoveWireguardKeys deletes wgN.key and wgN.pub from // RemoveWireguardKeys deletes wgN.key and wgN.pub from
// the machine's config directory // the machine's config directory
func (m *Machine) RemoveWireguardKeys(ringID rings.RingID) error { func (m *Machine) RemoveWireguardKeys(ring int) error {
ring, err := AsWireguardInterfaceID(ringID) var err error
if err != nil {
return err
}
keyFile, pubFile, _ := ring.Files() err = m.RemoveFile("wg%v.pub", ring)
err = m.RemoveFile(pubFile)
switch { switch {
case os.IsNotExist(err): case os.IsNotExist(err):
// ignore // ignore
@@ -99,7 +86,7 @@ func (m *Machine) RemoveWireguardKeys(ringID rings.RingID) error {
return err return err
} }
err = m.RemoveFile(keyFile) err = m.RemoveFile("wg%v.key", ring)
if os.IsNotExist(err) { if os.IsNotExist(err) {
// ignore // ignore
err = nil err = nil
@@ -109,13 +96,8 @@ func (m *Machine) RemoveWireguardKeys(ringID rings.RingID) error {
} }
// GetWireguardConfig reads a wgN.conf file // GetWireguardConfig reads a wgN.conf file
func (m *Machine) GetWireguardConfig(ringID rings.RingID) (*wireguard.Config, error) { func (m *Machine) GetWireguardConfig(ring int) (*wireguard.Config, error) {
ring, err := AsWireguardInterfaceID(ringID) data, err := m.ReadFile("wg%v.conf", ring)
if err != nil {
return nil, err
}
data, err := m.ReadFile(ring.ConfFile())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -124,7 +106,7 @@ func (m *Machine) GetWireguardConfig(ringID rings.RingID) (*wireguard.Config, er
return wireguard.NewConfigFromReader(r) return wireguard.NewConfigFromReader(r)
} }
func (m *Machine) tryApplyWireguardConfig(ring rings.RingID) error { func (m *Machine) tryApplyWireguardConfig(ring int) error {
wg, err := m.GetWireguardConfig(ring) wg, err := m.GetWireguardConfig(ring)
switch { switch {
case os.IsNotExist(err): 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() addr := wg.GetAddress()
if !core.IsZero(addr) { if !core.IsZero(addr) {
regionID, zoneID, nodeID, ok := Rings[ring].Decode(addr) zoneID, nodeID, ok := Rings[ring].Decode(addr)
if !ok { if !ok {
return fmt.Errorf("%s: invalid address", addr) 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) return core.Wrap(err, "%s: invalid address", addr)
} }
} }
@@ -156,7 +138,7 @@ func (m *Machine) applyWireguardConfigNode(ring rings.RingID, wg *wireguard.Conf
return nil 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 { if err := m.applyWireguardConfigNode(ring, wg); err != nil {
return err return err
} }
@@ -170,7 +152,7 @@ func (m *Machine) applyWireguardConfig(ring rings.RingID, wg *wireguard.Config)
WithField("subsystem", "wireguard"). WithField("subsystem", "wireguard").
WithField("node", m.Name). WithField("node", m.Name).
WithField("peer", peer.Endpoint.Host). WithField("peer", peer.Endpoint.Host).
WithField("ring", MustWireguardInterfaceID(ring)). WithField("ring", ring).
Print("ignoring unknown endpoint") Print("ignoring unknown endpoint")
case err != nil: case err != nil:
return core.Wrap(err, "peer") return core.Wrap(err, "peer")
@@ -180,9 +162,9 @@ func (m *Machine) applyWireguardConfig(ring rings.RingID, wg *wireguard.Config)
return nil return nil
} }
func (m *Machine) getRingInfo(ring rings.RingID) (*RingInfo, bool) { func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) {
for _, ri := range m.Rings { for _, ri := range m.Rings {
if ri.RingID() == ring { if ri.Ring == ring {
return ri, ri.Enabled return ri, ri.Enabled
} }
} }
@@ -190,13 +172,13 @@ func (m *Machine) getRingInfo(ring rings.RingID) (*RingInfo, bool) {
return nil, false 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) cur, _ := m.getRingInfo(ring)
if cur == nil { if cur == nil {
// first, append // first, append
m.debug(). m.debug().
WithField("node", m.Name). WithField("node", m.Name).
WithField("ring", MustWireguardInterfaceID(ring)). WithField("ring", ring).
Print("found") Print("found")
m.Rings = append(m.Rings, new) m.Rings = append(m.Rings, new)
return nil return nil
@@ -206,11 +188,9 @@ func (m *Machine) applyRingInfo(ring rings.RingID, new *RingInfo) error {
return cur.Merge(new) return cur.Merge(new)
} }
func (m *Machine) applyWireguardInterfaceConfig(ring rings.RingID, func (m *Machine) applyWireguardInterfaceConfig(ring int, data wireguard.InterfaceConfig) error {
data wireguard.InterfaceConfig) error {
//
ri := &RingInfo{ ri := &RingInfo{
Ring: MustWireguardInterfaceID(ring), Ring: ring,
Enabled: true, Enabled: true,
Keys: wireguard.KeyPair{ Keys: wireguard.KeyPair{
PrivateKey: data.PrivateKey, PrivateKey: data.PrivateKey,
@@ -220,9 +200,7 @@ func (m *Machine) applyWireguardInterfaceConfig(ring rings.RingID,
return m.applyRingInfo(ring, ri) return m.applyRingInfo(ring, ri)
} }
func (m *Machine) applyWireguardPeerConfig(ring rings.RingID, func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) error {
pc wireguard.PeerConfig) error {
//
peer, found := m.getPeerByName(pc.Endpoint.Name()) peer, found := m.getPeerByName(pc.Endpoint.Name())
switch { switch {
case !found: case !found:
@@ -234,7 +212,7 @@ func (m *Machine) applyWireguardPeerConfig(ring rings.RingID,
default: default:
// apply RingInfo // apply RingInfo
ri := &RingInfo{ ri := &RingInfo{
Ring: MustWireguardInterfaceID(ring), Ring: ring,
Enabled: true, Enabled: true,
Keys: wireguard.KeyPair{ Keys: wireguard.KeyPair{
PublicKey: pc.PublicKey, PublicKey: pc.PublicKey,
@@ -245,29 +223,21 @@ func (m *Machine) applyWireguardPeerConfig(ring rings.RingID,
} }
} }
func (m *Machine) applyZoneNodeID(regionID rings.RegionID, func (m *Machine) applyZoneNodeID(zoneID ZoneID, nodeID NodeID) error {
zoneID rings.ZoneID, nodeID rings.NodeID) error {
//
switch { switch {
case !regionID.Valid(): case zoneID == 0:
return fmt.Errorf("invalid %s", "regionID")
case !zoneID.Valid():
return fmt.Errorf("invalid %s", "zoneID") return fmt.Errorf("invalid %s", "zoneID")
case !nodeID.Valid(): case nodeID == 0:
return fmt.Errorf("invalid %s", "nodeID") return fmt.Errorf("invalid %s", "nodeID")
case m.ID != 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: case m.zone.ID != 0 && m.zone.ID != zoneID:
return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.zone.ID, zoneID) return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.zone.ID, zoneID)
case m.Region() != regionID: case m.zone.ID == 0:
return fmt.Errorf("invalid %s: %v ≠ %v", "regionID", m.Region(), regionID) m.zone.ID = zoneID
default:
if m.zone.ID == 0 {
m.zone.ID = zoneID
}
return nil
} }
return nil
} }
func (m *Machine) setRingDefaults(ri *RingInfo) error { 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 // RemoveWireguardConfig deletes wgN.conf from the machine's
// config directory. // config directory.
func (m *Machine) RemoveWireguardConfig(ringID rings.RingID) error { func (m *Machine) RemoveWireguardConfig(ring int) error {
ring, err := AsWireguardInterfaceID(ringID) err := m.RemoveFile("wg%v.conf", ring)
if err != nil {
return err
}
err = m.RemoveFile(ring.ConfFile())
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = nil err = nil
} }
@@ -303,12 +268,7 @@ func (m *Machine) RemoveWireguardConfig(ringID rings.RingID) error {
return err return err
} }
func (m *Machine) createRingInfo(ringID rings.RingID, enabled bool) (*RingInfo, error) { func (m *Machine) createRingInfo(ring int, enabled bool) (*RingInfo, error) {
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
return nil, err
}
keys, err := wireguard.NewKeyPair() keys, err := wireguard.NewKeyPair()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -320,7 +280,7 @@ func (m *Machine) createRingInfo(ringID rings.RingID, enabled bool) (*RingInfo,
Keys: keys, Keys: keys,
} }
err = m.applyRingInfo(ringID, ri) err = m.applyRingInfo(ring, ri)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+6 -8
View File
@@ -9,8 +9,6 @@ import (
"time" "time"
"darvaza.org/core" "darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
// LookupNetIP uses the DNS Resolver to get the public addresses associated // 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) return core.Wrap(err, m.Name)
} }
for _, ring := range Rings { for i := 0; i < RingsCount; i++ {
if err := m.tryReadWireguardKeys(ring.ID); err != nil { if err := m.tryReadWireguardKeys(i); err != nil {
return core.Wrap(err, m.Name) return core.Wrap(err, m.Name)
} }
} }
@@ -67,18 +65,18 @@ func (m *Machine) setID() error {
return err return err
} }
m.ID = rings.NodeID(id) m.ID = NodeID(id)
return nil return nil
} }
// scan is called once we know about all zones and machine names // scan is called once we know about all zones and machine names
func (m *Machine) scan(_ *ScanOptions) error { func (m *Machine) scan(_ *ScanOptions) error {
for _, ring := range Rings { for i := 0; i < RingsCount; i++ {
if err := m.tryApplyWireguardConfig(ring.ID); err != nil { if err := m.tryApplyWireguardConfig(i); err != nil {
m.error(err). m.error(err).
WithField("subsystem", "wireguard"). WithField("subsystem", "wireguard").
WithField("node", m.Name). WithField("node", m.Name).
WithField("ring", MustWireguardInterfaceID(ring.ID)). WithField("ring", i).
Print() Print()
return err return err
} }
+2 -99
View File
@@ -3,8 +3,6 @@ package cluster
import ( import (
"bytes" "bytes"
"path/filepath" "path/filepath"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
var ( var (
@@ -26,15 +24,8 @@ type Region struct {
zones []*Zone zones []*Zone
Name string Name string
ID rings.RegionID `json:",omitempty" yaml:",omitempty"` Cluster *string `json:",omitempty" yaml:",omitempty"`
Cluster *string `json:",omitempty" yaml:",omitempty"` Regions []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
} }
// ForEachRegion calls a function for each Region of the cluster // ForEachRegion calls a function for each Region of the cluster
@@ -101,8 +92,6 @@ func (m *Cluster) initRegions(_ *ScanOptions) error {
} }
m.sortRegions() m.sortRegions()
m.scanRegionID()
m.computeZonesRegion()
return nil return nil
} }
@@ -221,92 +210,6 @@ func (m *Cluster) finishRegion(r *Region) {
r.Regions = sub 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) { func (m *Cluster) getRegion(name string) (*Region, bool) {
for i := range m.Regions { for i := range m.Regions {
r := &m.Regions[i] r := &m.Regions[i]
+108 -104
View File
@@ -4,86 +4,31 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"net/netip" "net/netip"
"strconv"
"git.jpi.io/amery/jpictl/pkg/rings"
"git.jpi.io/amery/jpictl/pkg/wireguard" "git.jpi.io/amery/jpictl/pkg/wireguard"
) )
const ( 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 is the port wireguard uses for ring0
RingZeroPort = 51800 RingZeroPort = 51800
// RingOnePort is the port wireguard uses for ring1 // RingOnePort is the port wireguard uses for ring1
RingOnePort = 51810 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 // RingInfo contains represents the Wireguard endpoint details
// for a Machine on a particular ring // for a Machine on a particular ring
type RingInfo struct { type RingInfo struct {
Ring WireguardInterfaceID Ring int
Enabled bool Enabled bool
Keys wireguard.KeyPair 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 // Merge attempts to combine two RingInfo structs
func (ri *RingInfo) Merge(alter *RingInfo) error { func (ri *RingInfo) Merge(alter *RingInfo) error {
switch { switch {
@@ -109,7 +54,7 @@ func (ri *RingInfo) unsafeMerge(alter *RingInfo) error {
ri.Enabled = true ri.Enabled = true
} }
// fill the gaps on our key pair // fill the gaps on our keypair
if ri.Keys.PrivateKey.IsZero() { if ri.Keys.PrivateKey.IsZero() {
ri.Keys.PrivateKey = alter.Keys.PrivateKey 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 // RingAddressEncoder provides encoder/decoder access for a particular
// Wireguard ring // Wireguard ring
type RingAddressEncoder struct { type RingAddressEncoder struct {
ID rings.RingID ID int
Port uint16 Port uint16
Encode func(rings.RegionID, rings.ZoneID, rings.NodeID) (netip.Addr, error) Encode func(zoneID ZoneID, nodeID NodeID) (netip.Addr, bool)
Decode func(addr netip.Addr) (rings.RegionID, rings.ZoneID, rings.NodeID, bool) Decode func(addr netip.Addr) (zoneID ZoneID, nodeID NodeID, ok bool)
} }
var ( var (
// RingZero is a wg0 address encoder/decoder // RingZero is a wg0 address encoder/decoder
RingZero = RingAddressEncoder{ RingZero = RingAddressEncoder{
ID: rings.RingZeroID, ID: 0,
Port: RingZeroPort, Port: RingZeroPort,
Decode: rings.DecodeRingZeroAddress, Decode: ParseRingZeroAddress,
Encode: rings.RingZeroAddress, Encode: RingZeroAddress,
} }
// RingOne is a wg1 address encoder/decoder // RingOne is a wg1 address encoder/decoder
RingOne = RingAddressEncoder{ RingOne = RingAddressEncoder{
ID: rings.RingOneID, ID: 1,
Port: RingOnePort, Port: RingOnePort,
Decode: rings.DecodeRingOneAddress, Decode: ParseRingOneAddress,
Encode: rings.RingOneAddress, Encode: RingOneAddress,
} }
// Rings provides indexed access to the ring address encoders // Rings provides indexed access to the ring address encoders
Rings = []RingAddressEncoder{ Rings = [RingsCount]RingAddressEncoder{
RingZero, RingZero,
RingOne, 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 ( var (
_ MachineIterator = (*Ring)(nil) _ MachineIterator = (*Ring)(nil)
_ ZoneIterator = (*Ring)(nil) _ ZoneIterator = (*Ring)(nil)
@@ -184,15 +203,14 @@ func (r *Ring) AddPeer(p *Machine) bool {
nodeID := p.ID nodeID := p.ID
zoneID := p.Zone() zoneID := p.Zone()
regionID := p.Region() addr, _ := r.Encode(zoneID, nodeID)
addr, _ := r.Encode(regionID, zoneID, nodeID)
rp := &RingPeer{ rp := &RingPeer{
Node: p, Node: p,
Address: addr, Address: addr,
PrivateKey: ri.Keys.PrivateKey, PrivateKey: ri.Keys.PrivateKey,
PeerConfig: wireguard.PeerConfig{ 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, PublicKey: ri.Keys.PublicKey,
Endpoint: wireguard.EndpointAddress{ Endpoint: wireguard.EndpointAddress{
Host: p.FullName(), Host: p.FullName(),
@@ -215,27 +233,27 @@ func (r *Ring) AddPeer(p *Machine) bool {
} }
func (r *Ring) setRingZeroAllowedIPs(rp *RingPeer) { 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 // everyone on ring0 is a gateway to ring1
subnet, _ := rings.RingOnePrefix(regionID, zoneID) addr, _ := RingOneAddress(zoneID, 0)
rp.AllowSubnet(subnet) rp.AllowCIDR(addr, 12)
// peer // peer
rp.AllowCIDR(rp.Address, 32) rp.AllowCIDR(rp.Address, 32)
} }
func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) { func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) {
regionID, zoneID, _, _ := r.Decode(rp.Address) zoneID, _, _ := r.Decode(rp.Address)
// peer // peer
rp.AllowCIDR(rp.Address, 32) rp.AllowCIDR(rp.Address, 32)
// ring1 gateways connect to all other ring1 networks // ring1 gateways connect to all other ring1 networks
r.ForEachZone(func(z *Zone) bool { r.ForEachZone(func(z *Zone) bool {
if !z.Is(regionID, zoneID) { if z.ID != zoneID {
subnet := z.RingOnePrefix() addr, _ := r.Encode(z.ID, 0)
rp.AllowSubnet(subnet) rp.AllowCIDR(addr, 12)
} }
return false return false
}) })
@@ -244,7 +262,7 @@ func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) {
r.ForEachZone(func(z *Zone) bool { r.ForEachZone(func(z *Zone) bool {
z.ForEachMachine(func(p *Machine) bool { z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() { if p.IsGateway() {
addr, _ := p.RingZeroAddress() addr, _ := RingZeroAddress(z.ID, p.ID)
rp.AllowCIDR(addr, 32) rp.AllowCIDR(addr, 32)
} }
return false return false
@@ -311,29 +329,15 @@ type RingPeer struct {
// AllowCIDR allows an IP range via this peer // AllowCIDR allows an IP range via this peer
func (rp *RingPeer) AllowCIDR(addr netip.Addr, bits int) { func (rp *RingPeer) AllowCIDR(addr netip.Addr, bits int) {
rp.AllowSubnet(netip.PrefixFrom(addr, bits)) cidr := netip.PrefixFrom(addr, bits)
} rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, cidr)
// AllowSubnet allows an IP range via this peer
func (rp *RingPeer) AllowSubnet(subnet netip.Prefix) {
rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, subnet)
} }
// NewRing composes a new Ring for Wireguard setup // NewRing composes a new Ring for Wireguard setup
func NewRing(z ZoneIterator, m MachineIterator, ringID rings.RingID) (*Ring, error) { func NewRing(z ZoneIterator, m MachineIterator, ring int) (*Ring, error) {
var r *Ring r := &Ring{
for _, ring := range Rings { RingAddressEncoder: Rings[ring],
if ringID == ring.ID { ZoneIterator: z,
r = &Ring{
RingAddressEncoder: ring,
ZoneIterator: z,
}
break
}
}
if r == nil {
return nil, ErrInvalidRing(ringID)
} }
m.ForEachMachine(func(p *Machine) bool { m.ForEachMachine(func(p *Machine) bool {
+3 -3
View File
@@ -35,13 +35,13 @@ func (m *Cluster) SyncMkdirAll() error {
func (m *Cluster) SyncAllWireguard() error { func (m *Cluster) SyncAllWireguard() error {
var err error var err error
for _, ring := range Rings { for ring := 0; ring < RingsCount; ring++ {
err = m.WriteWireguardKeys(ring.ID) err = m.WriteWireguardKeys(ring)
if err != nil { if err != nil {
return err return err
} }
err = m.SyncWireguardConfig(ring.ID) err = m.SyncWireguardConfig(ring)
if err != nil { if err != nil {
return err 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 ( import (
"io/fs" "io/fs"
"os" "os"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
var ( var (
@@ -28,22 +26,22 @@ var (
// A WireguardConfigPruner deletes wgN.conf on all machines under // A WireguardConfigPruner deletes wgN.conf on all machines under
// its scope with the specified ring disabled // its scope with the specified ring disabled
type WireguardConfigPruner interface { type WireguardConfigPruner interface {
PruneWireguardConfig(ring rings.RingID) error PruneWireguardConfig(ring int) error
} }
// PruneWireguardConfig removes wgN.conf files of machines with // PruneWireguardConfig removes wgN.conf files of machines with
// the corresponding ring disabled on all zones // 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) return pruneWireguardConfig(m, ring)
} }
// PruneWireguardConfig removes wgN.conf files of machines with // PruneWireguardConfig removes wgN.conf files of machines with
// the corresponding ring disabled. // the corresponding ring disabled.
func (z *Zone) PruneWireguardConfig(ring rings.RingID) error { func (z *Zone) PruneWireguardConfig(ring int) error {
return pruneWireguardConfig(z, ring) return pruneWireguardConfig(z, ring)
} }
func pruneWireguardConfig(m MachineIterator, ring rings.RingID) error { func pruneWireguardConfig(m MachineIterator, ring int) error {
var err error var err error
m.ForEachMachine(func(p *Machine) bool { 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 // PruneWireguardConfig deletes the wgN.conf file if its
// presence on the ring is disabled // 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) _, ok := m.getRingInfo(ring)
if !ok { if !ok {
return m.RemoveWireguardConfig(ring) 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 // A WireguardConfigWriter rewrites all wgN.conf on all machines under
// its scope attached to that ring // its scope attached to that ring
type WireguardConfigWriter interface { type WireguardConfigWriter interface {
WriteWireguardConfig(ring rings.RingID) error WriteWireguardConfig(ring int) error
} }
// WriteWireguardConfig rewrites all wgN.conf on all machines // WriteWireguardConfig rewrites all wgN.conf on all machines
// attached to that ring // attached to that ring
func (m *Cluster) WriteWireguardConfig(ring rings.RingID) error { func (m *Cluster) WriteWireguardConfig(ring int) error {
switch ring { switch ring {
case rings.RingZeroID: case 0:
return writeWireguardConfig(m, m, ring) return writeWireguardConfig(m, m, ring)
case rings.RingOneID: case 1:
var err error var err error
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
err = writeWireguardConfig(m, z, ring) err = writeWireguardConfig(m, z, ring)
@@ -90,24 +88,24 @@ func (m *Cluster) WriteWireguardConfig(ring rings.RingID) error {
}) })
return err return err
default: default:
return ErrInvalidRing(ring) return fs.ErrInvalid
} }
} }
// WriteWireguardConfig rewrites all wgN.conf on all machines // WriteWireguardConfig rewrites all wgN.conf on all machines
// on the Zone attached to that ring // on the Zone attached to that ring
func (z *Zone) WriteWireguardConfig(ring rings.RingID) error { func (z *Zone) WriteWireguardConfig(ring int) error {
switch ring { switch ring {
case rings.RingZeroID: case 0:
return writeWireguardConfig(z.zones, z.zones, ring) return writeWireguardConfig(z.zones, z.zones, ring)
case rings.RingOneID: case 1:
return writeWireguardConfig(z.zones, z, ring) return writeWireguardConfig(z.zones, z, ring)
default: 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) r, err := NewRing(z, m, ring)
if err != nil { if err != nil {
return err return err
@@ -123,7 +121,7 @@ func writeWireguardConfig(z ZoneIterator, m MachineIterator, ring rings.RingID)
// WriteWireguardConfig rewrites the wgN.conf file of this Machine // WriteWireguardConfig rewrites the wgN.conf file of this Machine
// if enabled // 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) r, err := NewRing(m.zone.zones, m.zone, ring)
if err != nil { if err != nil {
return err return err
@@ -133,17 +131,12 @@ func (m *Machine) WriteWireguardConfig(ring rings.RingID) error {
} }
func (m *Machine) writeWireguardRingConfig(r *Ring) error { func (m *Machine) writeWireguardRingConfig(r *Ring) error {
ring, err := AsWireguardInterfaceID(r.ID)
if err != nil {
return err
}
wg, err := r.ExportConfig(m) wg, err := r.ExportConfig(m)
if err != nil { if err != nil {
return nil return nil
} }
f, err := m.CreateTruncFile(ring.ConfFile()) f, err := m.CreateTruncFile("wg%v.conf", r.ID)
if err != nil { if err != nil {
return err return err
} }
@@ -156,16 +149,16 @@ func (m *Machine) writeWireguardRingConfig(r *Ring) error {
// A WireguardConfigSyncer updates all wgN.conf on all machines under // A WireguardConfigSyncer updates all wgN.conf on all machines under
// its scope reflecting the state of the ring // its scope reflecting the state of the ring
type WireguardConfigSyncer interface { type WireguardConfigSyncer interface {
SyncWireguardConfig(ring rings.RingID) error SyncWireguardConfig(ring int) error
} }
// SyncWireguardConfig updates all wgN.conf files for the specified // SyncWireguardConfig updates all wgN.conf files for the specified
// ring // ring
func (m *Cluster) SyncWireguardConfig(ring rings.RingID) error { func (m *Cluster) SyncWireguardConfig(ring int) error {
switch ring { switch ring {
case rings.RingZeroID: case 0:
return syncWireguardConfig(m, m, ring) return syncWireguardConfig(m, m, ring)
case rings.RingOneID: case 1:
var err error var err error
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
err = syncWireguardConfig(m, z, ring) err = syncWireguardConfig(m, z, ring)
@@ -173,24 +166,24 @@ func (m *Cluster) SyncWireguardConfig(ring rings.RingID) error {
}) })
return err return err
default: default:
return ErrInvalidRing(ring) return fs.ErrInvalid
} }
} }
// SyncWireguardConfig updates all wgN.conf files for the specified // SyncWireguardConfig updates all wgN.conf files for the specified
// ring // ring
func (z *Zone) SyncWireguardConfig(ring rings.RingID) error { func (z *Zone) SyncWireguardConfig(ring int) error {
switch ring { switch ring {
case rings.RingZeroID: case 0:
return syncWireguardConfig(z.zones, z.zones, ring) return syncWireguardConfig(z.zones, z.zones, ring)
case rings.RingOneID: case 1:
return syncWireguardConfig(z.zones, z, ring) return syncWireguardConfig(z.zones, z, ring)
default: 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) r, err := NewRing(z, m, ring)
if err != nil { if err != nil {
return err 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 // SyncWireguardConfig updates all wgN.conf files for the specified
// ring // ring
func (m *Machine) SyncWireguardConfig(ring rings.RingID) error { func (m *Machine) SyncWireguardConfig(ring int) error {
return m.zone.SyncWireguardConfig(ring) return m.zone.SyncWireguardConfig(ring)
} }
// A WireguardKeysWriter writes the Wireguard Keys for all machines // A WireguardKeysWriter writes the Wireguard Keys for all machines
// under its scope for the specified ring // under its scope for the specified ring
type WireguardKeysWriter interface { type WireguardKeysWriter interface {
WriteWireguardKeys(ring rings.RingID) error WriteWireguardKeys(ring int) error
} }
// WriteWireguardKeys rewrites all wgN.{key,pub} files // 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) return writeWireguardKeys(m, ring)
} }
// WriteWireguardKeys rewrites all wgN.{key,pub} files on this zone // 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) return writeWireguardKeys(z, ring)
} }
func writeWireguardKeys(m MachineIterator, ring rings.RingID) error { func writeWireguardKeys(m MachineIterator, ring int) error {
var err error var err error
m.ForEachMachine(func(p *Machine) bool { 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 // 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 err error
var key, pub string var key, pub string
var ri *RingInfo var ri *RingInfo
ri, _ = m.getRingInfo(ringID) ri, _ = m.getRingInfo(ring)
if ri != nil { if ri != nil {
key = ri.Keys.PrivateKey.String() key = ri.Keys.PrivateKey.String()
pub = ri.Keys.PublicKey.String() pub = ri.Keys.PublicKey.String()
@@ -265,13 +258,12 @@ func (m *Machine) WriteWireguardKeys(ringID rings.RingID) error {
pub = ri.Keys.PrivateKey.Public().String() pub = ri.Keys.PrivateKey.Public().String()
} }
keyFile, pubFile, _ := ri.Ring.Files() err = m.WriteStringFile(key+"\n", "wg%v.key", ring)
err = m.WriteStringFile(key+"\n", keyFile)
if err != nil { if err != nil {
return err return err
} }
err = m.WriteStringFile(pub+"\n", pubFile) err = m.WriteStringFile(pub+"\n", "wg%v.pub", ring)
if err != nil { if err != nil {
return err return err
} }
+4 -42
View File
@@ -2,8 +2,6 @@ package cluster
import ( import (
"io/fs" "io/fs"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
var ( var (
@@ -19,10 +17,9 @@ type ZoneIterator interface {
// affinity. // affinity.
type Zone struct { type Zone struct {
zones *Cluster zones *Cluster
region *Region
logger `json:"-" yaml:"-"` logger `json:"-" yaml:"-"`
ID rings.ZoneID ID ZoneID
Name string Name string
Regions []string `json:",omitempty" yaml:",omitempty"` 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 // 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 err error
var found bool 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 // GatewayIDs returns the list of IDs of machines that act as ring0 gateways
func (z *Zone) GatewayIDs() ([]rings.NodeID, int) { func (z *Zone) GatewayIDs() ([]NodeID, int) {
var out []rings.NodeID var out []NodeID
z.ForEachMachine(func(p *Machine) bool { z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() { if p.IsGateway() {
out = append(out, p.ID) out = append(out, p.ID)
@@ -70,38 +67,3 @@ func (z *Zone) GatewayIDs() ([]rings.NodeID, int) {
return out, len(out) 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 package rings
import ( import (
"fmt"
"strconv"
"syscall" "syscall"
"darvaza.org/core" "darvaza.org/core"
) )
const ( 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 indicates the highest number that can be used for a [RegionID].
RegionMax = (1 << 4) - 1 RegionMax = (1 << 4) - 1
// ZoneMax indicates the highest number that can be used for a [ZoneID]. // ZoneMax indicates the highest number that can be used for a [ZoneID].
ZoneMax = (1 << 4) - 1 ZoneMax = (1 << 4) - 1
// NodeMax indicates the highest number that can be used for a [NodeID]. // 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] // NodeZeroMax indicates the highest number that can be used for a [NodeID]
// when its a gateway connected to Ring 0 (backbone). // 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 indicates the size of the prefix on the ring 0 (backbone) network.
RingZeroBits = 16 RingZeroBits = 16
@@ -44,41 +31,18 @@ const (
RingThreeBits = 12 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]. // RegionID is the identifier of a region, valid between 1 and [RegionMax].
type RegionID int type RegionID int
// Valid tells a [RegionID] is within the valid range. // Valid tells a [RegionID] is within the valid range.
func (n RegionID) Valid() bool { return n > 0 && n <= RegionMax } 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]. // ZoneID is the identifier of a zone within a region, valid between 1 and [ZoneMax].
type ZoneID int type ZoneID int
// Valid tells a [ZoneID] is within the valid range. // Valid tells a [ZoneID] is within the valid range.
func (n ZoneID) Valid() bool { return n > 0 && n <= ZoneMax } 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 // 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. // 1 and [NodeMax], but between 1 and [NodeZeroMax] if it will be a zone gateway.
type NodeID int 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. // 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) 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 // ErrOutOfRange is an error indicating the value of a field
// is out of range. // is out of range.
func ErrOutOfRange[T ~int | ~uint32](value T, field string) error { func ErrOutOfRange[T ~int | ~uint32](value T, field string) error {
return core.Wrap(syscall.EINVAL, "%s out of range (%v)", field, value) 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))
}
}