Compare commits

..

26 Commits

Author SHA1 Message Date
amery f9462d3e0b Merge pull request (#55)
introduce `jpictl list`, `tools.LazyBuffer` and updated build system

Reviewed-on: #55
2024-07-25 20:22:17 +02:00
amery c20bd3d41f jpictl: introduce initial jpictl list
to see the different networks and addresses

currently limited to rings zero and one.

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-07-25 17:36:59 +00:00
amery f0cef6c19f tools: introduce LazyClose()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-07-25 17:28:56 +00:00
amery 05538e6925 tools: introduce LazyBuffer abstraction of bytes.Buffer
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-07-25 17:27:28 +00:00
amery 0c21d74c15 rings: make unsafe address factories public
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-07-25 17:17:57 +00:00
amery 401603f4be chore: clear lint warnings
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-07-25 17:16:02 +00:00
amery ac43ee869c build-sys: update build sys to darvaza.org/x's latest
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-07-25 17:12:57 +00:00
amery 4345a17d9a Merge pull request 'cluster: fix wg0.conf generator' (#54) from pr-amery-wg0.conf into main
Reviewed-on: #54
2024-07-24 19:31:22 +02:00
amery 01ef75a020 cluster: fix wg0.conf generator
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-07-24 17:30:10 +00:00
amery d0efcbaa74 Merge pull request 'cluster: rework env output to qualify zones using the region' (#53) from pr-amery-env into main
Reviewed-on: #53
2024-07-17 17:58:47 +02:00
amery 7f5d48b2b0 cluster: rework env output to qualify zones using the region
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-07-17 15:54:35 +00:00
karasz 00b2f8b531 Merge pull request 'cluster: migrate to using pkg/rings for Addresses' (#51) from pr-amery-rings into main
Reviewed-on: #51
2024-06-04 10:53:13 +02:00
amery 948eff76d3 cluster: migrate to using pkg/rings for Addresses
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 20:45:29 +00:00
amery 187149c129 cluster: decouple RingID from WireguardInterfaceID
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 20:45:29 +00:00
amery 879ee69f07 Merge pull request #50
Reviewed-on: #50
2024-06-03 22:17:26 +02:00
karasz 932a41a3ac Merge pull request 'cluster: use typed IDs, introduce Region.ID, and pre-compute primary region' (#49) from pr-amery-regions into main
Reviewed-on: #49
2024-06-03 17:22:03 +02:00
amery bcb20ab1e6 rings: introduce ring-specific decoders
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 14:26:54 +00:00
amery 96c59dfe8a rings: introduce a generic DecodeAddress() for all four rings
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 14:17:35 +00:00
amery 169a1e9602 rings: introduce RingID and its values
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 14:14:51 +00:00
amery 982e16581c cluster: pre-compute Zone's primary region
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 13:59:31 +00:00
amery cede95e119 cluster: run initRegions() before scanZoneIDs()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 13:59:31 +00:00
amery 14e1c447c9 cluster: assign valid rings.RegionID to each primary region
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 13:59:31 +00:00
amery 99dece1e43 cluster: use rings.ZoneID and rings.NodeID types
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 13:59:31 +00:00
amery 6d89e0ea3c rings: fix NodeZeroMax/NodeMax to not accept .255
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 13:59:09 +00:00
amery ff8f2c6ea1 rings: add String() to RegionID, ZoneID, NodeID
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 13:59:09 +00:00
amery 83921c1e13 build-sys: use revive 1.3.7 instead of master
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 13:59:09 +00:00
32 changed files with 1258 additions and 455 deletions
+14 -15
View File
@@ -6,19 +6,20 @@ GOFMT ?= gofmt
GOFMT_FLAGS = -w -l -s
GOGENERATE_FLAGS = -v
GOPATH ?= $(shell $(GO) env GOPATH)
GOBIN ?= $(GOPATH)/bin
TOOLSDIR := $(CURDIR)/pkg/tools
TMPDIR ?= .tmp
TMPDIR ?= $(CURDIR)/.tmp
OUTDIR ?= $(TMPDIR)
GOLANGCI_LINT_VERSION ?= v1.59.1
REVIVE_VERSION ?= v1.3.7
GOLANGCI_LINT_URL ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
GOLANGCI_LINT ?= $(GO) run $(GOLANGCI_LINT_URL)
REVIVE ?= $(GOBIN)/revive
REVIVE_CONF ?= $(TOOLSDIR)/revive.toml
REVIVE_RUN_ARGS ?= -config $(REVIVE_CONF) -formatter friendly
REVIVE_INSTALL_URL ?= github.com/mgechev/revive@master
GO_INSTALL_URLS = \
$(REVIVE_INSTALL_URL) \
REVIVE_URL ?= github.com/mgechev/revive@$(REVIVE_VERSION)
REVIVE ?= $(GO) run $(REVIVE_URL)
V = 0
Q = $(if $(filter 1,$V),,@)
@@ -29,12 +30,13 @@ GO_BUILD_CMD = $(GO_BUILD) -o "$(OUTDIR)"
all: get generate tidy build
install:
$Q $(GO) install -v ./cmd/...
clean: ; $(info $(M) cleaning)
rm -rf $(TMPDIR)
install: ; $(info $(M) cleaning)
$Q $(GO) install -v ./cmd/...
$(TMPDIR)/index: $(TOOLSDIR)/gen_index.sh Makefile FORCE ; $(info $(M) generating index)
$Q mkdir -p $(@D)
$Q $< > $@~
@@ -54,6 +56,3 @@ tidy: fmt
generate: ; $(info $(M) running go:generate)
$Q git grep -l '^//go:generate' | sort -uV | xargs -r -n1 $(GO) generate $(GOGENERATE_FLAGS)
$(REVIVE):
$Q $(GO) install -v $(REVIVE_INSTALL_URL)
+1 -1
View File
@@ -52,7 +52,7 @@ func populateDNSManager(mgr *dns.Manager, m *cluster.Cluster) error {
m.ForEachZone(func(z *cluster.Zone) bool {
z.ForEachMachine(func(p *cluster.Machine) bool {
err = mgr.AddHost(ctx, z.Name, p.ID, p.IsActive(), p.PublicAddresses...)
err = mgr.AddHost(ctx, z.Name, int(p.ID), p.IsActive(), p.PublicAddresses...)
return err != nil
})
+4 -5
View File
@@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"os"
"strconv"
"strings"
"github.com/spf13/cobra"
@@ -44,7 +43,7 @@ func gatewaySet(zi cluster.ZoneIterator, gw string) error {
zi.ForEachZone(func(z *cluster.Zone) bool {
for _, m := range z.Machines {
if m.Name == gw {
z.SetGateway(m.ID, true)
_ = z.SetGateway(m.ID, true)
return true
}
}
@@ -80,8 +79,8 @@ func gatewayUnset(zi cluster.ZoneIterator, ngw string) error {
zi.ForEachZone(func(z *cluster.Zone) bool {
for _, m := range z.Machines {
if m.Name == ngw && m.IsGateway() {
z.SetGateway(m.ID, false)
m.RemoveWireguardConfig(0)
_ = z.SetGateway(m.ID, false)
_ = m.RemoveWireguardConfig(0)
return true
}
}
@@ -128,7 +127,7 @@ func gatewayListAll(zi cluster.ZoneIterator) error {
return false
}
for _, i := range ids {
sIDs = append(sIDs, strconv.Itoa(i))
sIDs = append(sIDs, i.String())
}
b.WriteString(strings.Join(sIDs, ", "))
b.WriteString("\n")
+200
View File
@@ -0,0 +1,200 @@
package main
import (
"bytes"
"io"
"net/netip"
"os"
"darvaza.org/core"
"github.com/spf13/cobra"
"git.jpi.io/amery/jpictl/pkg/cluster"
"git.jpi.io/amery/jpictl/pkg/rings"
"git.jpi.io/amery/jpictl/pkg/tools"
)
type inventory struct {
r []*cluster.Region
z [][]*cluster.Zone
}
func (g *inventory) renderRingZero(out *tools.LazyBuffer) error {
ring0 := netip.PrefixFrom(rings.UnsafeRingZeroAddress(0, 0, 0), rings.RingZeroBits)
from, to, _ := rings.PrefixToRange(ring0)
_ = out.Printf("; wg%v\n", 0)
_ = out.Printf("%s\t%s-%s\n", ring0, from, to)
if err := g.renderRingZeroRegions(out); err != nil {
return err
}
return g.renderRingZeroZones(out)
}
func (g *inventory) renderRingZeroRegions(out *tools.LazyBuffer) error {
for _, r := range g.r {
if err := g.renderRingZeroRegion(out, r); err != nil {
return err
}
}
return nil
}
func (*inventory) renderRingZeroRegion(out *tools.LazyBuffer, r *cluster.Region) error {
addr := rings.UnsafeRingZeroAddress(r.ID, 0, 0)
ring0r := netip.PrefixFrom(addr, rings.RingZeroBits+4)
from, to, _ := rings.PrefixToRange(ring0r)
_ = out.Printf("%s\t%s-%s\t# %s\n", ring0r, from, to, r.Name)
return nil
}
func (g *inventory) renderRingZeroZones(out *tools.LazyBuffer) error {
for i, r := range g.r {
for _, z := range g.z[i] {
if err := g.renderRingZeroZone(out, r, z); err != nil {
return err
}
}
}
return nil
}
func (*inventory) renderRingZeroZone(out *tools.LazyBuffer, r *cluster.Region, z *cluster.Zone) error {
addr := rings.UnsafeRingZeroAddress(r.ID, z.ID, 0)
ring0rz := netip.PrefixFrom(addr, rings.RingZeroBits+4+4)
from, to, _ := rings.PrefixToRange(ring0rz)
_ = out.Printf("; wg%v: %s (%s)\n", 0, z.Name, r.Name)
_ = out.Printf("%s\t%s-%s\t%s\n", ring0rz, from, to, z.Name)
z.ForEachMachine(func(m *cluster.Machine) bool {
if m.IsGateway() {
addr, _ := m.RingZeroAddress()
cidr := netip.PrefixFrom(addr, 32)
_ = out.Printf("%s\t\t%s-%v\n", cidr, m.Name, 0)
}
return false
})
return nil
}
func (g *inventory) renderRingOne(out *tools.LazyBuffer) error {
for i, r := range g.r {
for _, z := range g.z[i] {
if err := g.renderRingOneZone(out, r, z); err != nil {
return err
}
}
}
return nil
}
func (*inventory) renderRingOneZone(out *tools.LazyBuffer, r *cluster.Region, z *cluster.Zone) error {
ring1, err := rings.RingOnePrefix(r.ID, z.ID)
if err != nil {
return err
}
from, to, _ := rings.PrefixToRange(ring1)
_ = out.Printf("; wg%v: %s (%s)\n", 1, z.Name, r.Name)
_ = out.Printf("%s\t%s-%s\t%s\n", ring1, from, to, z.Name)
z.ForEachMachine(func(m *cluster.Machine) bool {
addr := m.RingOneAddress()
cidr := netip.PrefixFrom(addr, 32)
_ = out.Printf("%s\t\t%s-%v\n", cidr, m.Name, 1)
return false
})
return nil
}
func (g *inventory) Marshal() ([]byte, error) {
var buf tools.LazyBuffer
if err := g.renderRingZero(&buf); err != nil {
return nil, err
}
if err := g.renderRingOne(&buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (g *inventory) WriteTo(out io.Writer) (int64, error) {
b, err := g.Marshal()
if err != nil {
return 0, err
}
buf := bytes.NewBuffer(b)
return buf.WriteTo(out)
}
func genInventory(m *cluster.Cluster) (*inventory, error) {
g := new(inventory)
g.populateRegions(m)
g.populateZones()
return g, nil
}
func (g *inventory) populateRegions(m *cluster.Cluster) {
m.ForEachRegion(func(r *cluster.Region) bool {
if r.IsPrimary() {
g.r = append(g.r, r)
}
return false
})
core.SliceSortFn(g.r, func(a, b *cluster.Region) bool {
return a.ID < b.ID
})
}
func (g *inventory) populateZones() {
g.z = make([][]*cluster.Zone, len(g.r))
for i, r := range g.r {
r.ForEachZone(func(z *cluster.Zone) bool {
g.z[i] = append(g.z[i], z)
return false
})
core.SliceSortFn(g.z[i], func(a, b *cluster.Zone) bool {
return a.ID < b.ID
})
}
}
// Command
var listCmd = &cobra.Command{
Use: "list",
Short: "list shows the IP/CIDR inventory",
PreRun: setVerbosity,
RunE: func(_ *cobra.Command, _ []string) error {
m, err := cfg.LoadZones(false)
if err != nil {
return err
}
out, err := genInventory(m)
if err != nil {
return err
}
_, err = out.WriteTo(os.Stdout)
return err
},
}
func init() {
rootCmd.AddCommand(listCmd)
}
+14 -23
View File
@@ -1,52 +1,43 @@
module git.jpi.io/amery/jpictl
go 1.19
go 1.21
require (
asciigoat.org/core v0.3.9 // indirect
asciigoat.org/ini v0.2.5
darvaza.org/core v0.13.1
darvaza.org/cache/x/simplelru v0.1.8 // indirect
darvaza.org/core v0.14.2
darvaza.org/resolver v0.9.2
darvaza.org/sidecar v0.4.0
darvaza.org/slog v0.5.7
darvaza.org/slog/handlers/discard v0.4.11
darvaza.org/slog/handlers/filter v0.4.9 // indirect
darvaza.org/slog/handlers/zerolog v0.4.9 // indirect
)
require (
github.com/gofrs/uuid/v5 v5.2.0
github.com/hack-pad/hackpadfs v0.2.1
github.com/libdns/cloudflare v0.1.1
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.23.0
golang.org/x/net v0.25.0
golang.org/x/crypto v0.25.0
golang.org/x/net v0.27.0
gopkg.in/yaml.v3 v3.0.1
)
require (
asciigoat.org/core v0.3.9 // indirect
darvaza.org/cache/x/simplelru v0.1.8 // indirect
darvaza.org/slog/handlers/filter v0.4.9 // indirect
darvaza.org/slog/handlers/zerolog v0.4.9 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect
github.com/miekg/dns v1.1.59 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
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.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.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
)
+13 -51
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.13.1 h1:ZoAfZ3OLnw+t28qMQQxXrDIkETmT2h5gAO6F1XuBpwg=
darvaza.org/core v0.13.1/go.mod h1:47Ydh67KnzjLNu1mzX3r2zpphbxQqEaihMsUq5GflQ4=
darvaza.org/core v0.14.2 h1:6p0iznuGfVGbBp+CnkZTw1b76j6Q/j4ffDztZXrrlK8=
darvaza.org/core v0.14.2/go.mod h1:C+B0GRNLB+/asGfxjQ9XZERdk7xaFxzt5xTIBPiNm2M=
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=
@@ -18,20 +18,9 @@ darvaza.org/slog/handlers/filter v0.4.9 h1:xD8OBwlJytpiwTSDDZqUuNSOsJuaManXQiOj9
darvaza.org/slog/handlers/filter v0.4.9/go.mod h1:t+sjcf1c46kAdf1TRiQmop91xlkteZrC4WDXoVwHgP8=
darvaza.org/slog/handlers/zerolog v0.4.9 h1:08FjRnwRGtJsLLBnbgxVorb/bkgm5QEM/LXD2cxeCbM=
darvaza.org/slog/handlers/zerolog v0.4.9/go.mod h1:PZYfx6eOxQfD+cXJQp52iwKgcD30QVYHoXxOCojAOdw=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc=
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
@@ -53,27 +42,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0=
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE=
github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA=
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
@@ -81,39 +53,29 @@ github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.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/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/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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
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/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.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=
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=
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=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+43
View File
@@ -0,0 +1,43 @@
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, _ := RingOneAddress(z.ID, p.ID)
addr := p.RingOneAddress()
cfg.Global.Monitors = append(cfg.Global.Monitors, p.Name)
cfg.Global.MonitorsAddr = append(cfg.Global.MonitorsAddr, addr)
+3 -6
View File
@@ -4,6 +4,7 @@ import (
"os"
"darvaza.org/slog"
"git.jpi.io/amery/jpictl/pkg/ceph"
)
@@ -14,8 +15,7 @@ type cephScanTODO struct {
func (todo *cephScanTODO) checkMachine(p *Machine) bool {
// on ceph all addresses are ring1
ring1, _ := RingOneAddress(p.Zone(), p.ID)
addr := ring1.String()
addr := p.RingOneAddress().String()
if _, found := todo.names[p.Name]; found {
// found on the TODO by name
@@ -73,10 +73,7 @@ func newCephScanTODO(cfg *ceph.Config) *cephScanTODO {
func (m *Cluster) scanCephMonitors(opts *ScanOptions) error {
cfg, err := m.GetCephConfig()
switch {
case os.IsNotExist(err):
err = nil
case err != nil:
if err != nil && !os.IsNotExist(err) {
return err
}
+5 -3
View File
@@ -6,16 +6,18 @@ 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
@@ -45,7 +47,7 @@ func (m *Cluster) initZones(opts *ScanOptions) error {
func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error {
var hasMissing bool
var lastMachineID int
var lastMachineID rings.NodeID
z.zones = m
z.logger = m
@@ -58,7 +60,7 @@ func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error {
case p.ID == 0:
hasMissing = true
case p.ID > lastMachineID:
lastMachineID = z.ID
lastMachineID = p.ID
}
return false
+4 -2
View File
@@ -7,6 +7,8 @@ import (
"strings"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
const (
@@ -23,9 +25,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,
} {
@@ -114,7 +116,7 @@ func (m *Cluster) scanMachines(opts *ScanOptions) error {
func (m *Cluster) scanZoneIDs(_ *ScanOptions) error {
var hasMissing bool
var lastZoneID int
var lastZoneID rings.ZoneID
m.ForEachZone(func(z *Zone) bool {
switch {
+107 -75
View File
@@ -4,8 +4,11 @@ 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
@@ -34,31 +37,49 @@ func (m *Cluster) Env(export bool) (*Env, error) {
return env, nil
}
// Zones returns the list of Zone IDs
func (m *Env) Zones() []int {
var zones []int
// 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
m.ForEachZone(func(z *Zone) bool {
iter := core.IIf[ZoneIterator](r != nil, r, m)
iter.ForEachZone(func(z *Zone) bool {
zones = append(zones, z.ID)
return false
})
core.SliceSortOrdered(zones)
return zones
}
// Regions returns the list of primary regions
func (m *Env) Regions() []string {
// RegionsNames returns a sorted list of primary regions names
func (m *Env) RegionsNames() []string {
var regions []string
m.ForEachRegion(func(r *Region) bool {
if r.Cluster != nil {
if r.IsPrimary() {
regions = append(regions, r.Name)
}
return false
})
sort.Strings(regions)
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)
return regions
}
@@ -70,67 +91,75 @@ func (m *Env) WriteTo(w io.Writer) (int64, error) {
m.writeEnvVar(&buf, m.cephFSID, "FSID")
}
m.writeEnvVarStrings(&buf, m.Regions(), "REGIONS")
m.writeEnvVarInts(&buf, m.Zones(), "ZONES")
m.ForEachZone(func(z *Zone) bool {
m.writeEnvZone(&buf, z)
return false
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, genEnvInts(ids), "REGIONS")
m.writeEnvVar(&buf, genEnvStrings(names), "REGIONS_NAMES")
for _, r := range regions {
m.writeEnvRegion(&buf, r)
}
return buf.WriteTo(w)
}
func (m *Env) writeEnvZone(w io.Writer, z *Zone) {
zoneID := z.ID
func (m *Env) getRegions() (out []*Region) {
m.ForEachRegion(func(r *Region) bool {
if r.IsPrimary() {
out = append(out, r)
}
return false
})
// ZONE{zoneID}
m.writeEnvVar(w, genEnvZoneNodes(z), "ZONE%v", zoneID)
core.SliceSortFn(out, func(a, b *Region) bool {
return a.ID < b.ID
})
// ZONE{zoneID}_NAME
m.writeEnvVar(w, z.Name, "ZONE%v_%s", zoneID, "NAME")
return out
}
func (m *Env) writeEnvRegion(w io.Writer, r *Region) {
regionID := r.ID
// ZONE{zoneID}_GW
// 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
gateways, _ := z.GatewayIDs()
m.writeEnvVarInts(w, gateways, "ZONE%v_%s", zoneID, "GW")
// ZONE{zoneID}_REGION
m.writeEnvVar(w, genEnvZoneRegion(z), "ZONE%v_%s", zoneID, "REGION")
m.writeEnvVar(w, genEnvInts(gateways), zonePrefix+"_GW")
// Ceph
monitors := z.GetCephMonitors()
// 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) writeEnvVarInts(w io.Writer, value []int, name string, args ...any) {
var buf bytes.Buffer
for _, v := range value {
if buf.Len() > 0 {
_, _ = fmt.Fprint(&buf, " ")
}
_, _ = fmt.Fprintf(&buf, "%v", v)
}
m.writeEnvVar(w, buf.String(), name, args...)
}
func (m *Env) writeEnvVarStrings(w io.Writer, value []string, name string, args ...any) {
var buf bytes.Buffer
for _, v := range value {
if buf.Len() > 0 {
_, _ = fmt.Fprint(&buf, " ")
}
_, _ = fmt.Fprintf(&buf, "%s", v)
}
m.writeEnvVar(w, buf.String(), name, args...)
// 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")
}
func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) {
@@ -155,6 +184,24 @@ func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) {
}
}
func genEnvInts[T core.Signed](values []T) string {
var buf bytes.Buffer
for _, v := range values {
if buf.Len() > 0 {
_, _ = buf.WriteRune(' ')
}
_, _ = buf.WriteString(fmt.Sprintf("%v", int64(v)))
}
return buf.String()
}
func genEnvStrings(values []string) string {
return strings.Join(values, " ")
}
func genEnvZoneNodes(z *Zone) string {
if n := z.Len(); n > 0 {
s := make([]string, 0, n)
@@ -164,26 +211,11 @@ func genEnvZoneNodes(z *Zone) string {
return false
})
return strings.Join(s, " ")
return genEnvStrings(s)
}
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 {
@@ -200,7 +232,7 @@ func genEnvZoneCephMonNames(m Machines) string {
func genEnvZoneCephMonIPs(m Machines) string {
var buf strings.Builder
m.ForEachMachine(func(p *Machine) bool {
addr, _ := RingOneAddress(p.Zone(), p.ID)
addr := p.RingOneAddress()
if buf.Len() > 0 {
_, _ = buf.WriteRune(' ')
+14 -1
View File
@@ -1,6 +1,13 @@
package cluster
import "errors"
import (
"errors"
"io/fs"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var (
// ErrInvalidName indicates the name isn't valid
@@ -14,3 +21,9 @@ 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, _ := RingOneAddress(p.zone.ID, p.ID)
ip := p.RingOneAddress()
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.ID == p.zone.ID {
if z.Is(p.Region(), p.Zone()) {
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, _ = RingZeroAddress(p.zone.ID, p.ID)
ip, _ = p.RingZeroAddress()
s = fmt.Sprintf("%s-%v", p.Name, 0)
entry = hostsEntry{
+12 -5
View File
@@ -3,6 +3,8 @@ package cluster
import (
"net/netip"
"strings"
"git.jpi.io/amery/jpictl/pkg/rings"
)
// revive:disable:line-length-limit
@@ -12,7 +14,7 @@ type Machine struct {
zone *Zone
logger `json:"-" yaml:"-"`
ID int
ID rings.NodeID
Name string `json:"-" yaml:"-"`
Inactive bool `json:"inactive,omitempty" yaml:"inactive,omitempty"`
@@ -51,13 +53,13 @@ func (m *Machine) IsActive() bool {
// IsGateway tells if the Machine is a ring0 gateway
func (m *Machine) IsGateway() bool {
_, ok := m.getRingInfo(0)
_, ok := m.getRingInfo(rings.RingZeroID)
return ok
}
// SetGateway enables/disables a Machine ring0 integration
func (m *Machine) SetGateway(enabled bool) error {
ri, found := m.getRingInfo(0)
ri, found := m.getRingInfo(rings.RingZeroID)
switch {
case !found && !enabled:
return nil
@@ -70,14 +72,19 @@ func (m *Machine) SetGateway(enabled bool) error {
}
ri.Enabled = enabled
return m.SyncWireguardConfig(0)
return m.SyncWireguardConfig(rings.RingZeroID)
}
// Zone indicates the [Zone] this machine belongs to
func (m *Machine) Zone() int {
func (m *Machine) Zone() rings.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)
}
+82 -42
View File
@@ -8,18 +8,26 @@ 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(ring int) (wireguard.KeyPair, error) {
func (m *Machine) GetWireguardKeys(ringID rings.RingID) (wireguard.KeyPair, error) {
var (
data []byte
err error
out wireguard.KeyPair
)
data, err = m.ReadFile("wg%v.key", ring)
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
// invalid ring
return out, err
}
keyFile, pubFile, _ := ring.Files()
data, err = m.ReadFile(keyFile)
if err != nil {
// failed to read
return out, err
@@ -28,11 +36,11 @@ func (m *Machine) GetWireguardKeys(ring int) (wireguard.KeyPair, error) {
out.PrivateKey, err = wireguard.PrivateKeyFromBase64(string(data))
if err != nil {
// bad key
err = core.Wrap(err, "wg%v.key", ring)
err = core.Wrap(err, keyFile)
return out, err
}
data, err = m.ReadFile("wg%v.pub", ring)
data, err = m.ReadFile(pubFile)
switch {
case os.IsNotExist(err):
// no wgN.pub is fine
@@ -44,7 +52,7 @@ func (m *Machine) GetWireguardKeys(ring int) (wireguard.KeyPair, error) {
out.PublicKey, err = wireguard.PublicKeyFromBase64(string(data))
if err != nil {
// bad key
err = core.Wrap(err, "wg%v.pub", ring)
err = core.Wrap(err, pubFile)
return out, err
}
}
@@ -53,8 +61,8 @@ func (m *Machine) GetWireguardKeys(ring int) (wireguard.KeyPair, error) {
return out, err
}
func (m *Machine) tryReadWireguardKeys(ring int) error {
kp, err := m.GetWireguardKeys(ring)
func (m *Machine) tryReadWireguardKeys(ringID rings.RingID) error {
kp, err := m.GetWireguardKeys(ringID)
switch {
case os.IsNotExist(err):
// ignore
@@ -65,20 +73,25 @@ func (m *Machine) tryReadWireguardKeys(ring int) error {
default:
// import keys
ri := &RingInfo{
Ring: ring,
Ring: MustWireguardInterfaceID(ringID),
Keys: kp,
}
return m.applyRingInfo(ring, ri)
return m.applyRingInfo(ringID, ri)
}
}
// RemoveWireguardKeys deletes wgN.key and wgN.pub from
// the machine's config directory
func (m *Machine) RemoveWireguardKeys(ring int) error {
var err error
func (m *Machine) RemoveWireguardKeys(ringID rings.RingID) error {
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
return err
}
err = m.RemoveFile("wg%v.pub", ring)
keyFile, pubFile, _ := ring.Files()
err = m.RemoveFile(pubFile)
switch {
case os.IsNotExist(err):
// ignore
@@ -86,7 +99,7 @@ func (m *Machine) RemoveWireguardKeys(ring int) error {
return err
}
err = m.RemoveFile("wg%v.key", ring)
err = m.RemoveFile(keyFile)
if os.IsNotExist(err) {
// ignore
err = nil
@@ -96,8 +109,13 @@ func (m *Machine) RemoveWireguardKeys(ring int) error {
}
// GetWireguardConfig reads a wgN.conf file
func (m *Machine) GetWireguardConfig(ring int) (*wireguard.Config, error) {
data, err := m.ReadFile("wg%v.conf", ring)
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())
if err != nil {
return nil, err
}
@@ -106,7 +124,7 @@ func (m *Machine) GetWireguardConfig(ring int) (*wireguard.Config, error) {
return wireguard.NewConfigFromReader(r)
}
func (m *Machine) tryApplyWireguardConfig(ring int) error {
func (m *Machine) tryApplyWireguardConfig(ring rings.RingID) error {
wg, err := m.GetWireguardConfig(ring)
switch {
case os.IsNotExist(err):
@@ -118,15 +136,15 @@ func (m *Machine) tryApplyWireguardConfig(ring int) error {
}
}
func (m *Machine) applyWireguardConfigNode(ring int, wg *wireguard.Config) error {
func (m *Machine) applyWireguardConfigNode(ring rings.RingID, wg *wireguard.Config) error {
addr := wg.GetAddress()
if !core.IsZero(addr) {
zoneID, nodeID, ok := Rings[ring].Decode(addr)
regionID, zoneID, nodeID, ok := Rings[ring].Decode(addr)
if !ok {
return fmt.Errorf("%s: invalid address", addr)
}
if err := m.applyZoneNodeID(zoneID, nodeID); err != nil {
if err := m.applyZoneNodeID(regionID, zoneID, nodeID); err != nil {
return core.Wrap(err, "%s: invalid address", addr)
}
}
@@ -138,7 +156,7 @@ func (m *Machine) applyWireguardConfigNode(ring int, wg *wireguard.Config) error
return nil
}
func (m *Machine) applyWireguardConfig(ring int, wg *wireguard.Config) error {
func (m *Machine) applyWireguardConfig(ring rings.RingID, wg *wireguard.Config) error {
if err := m.applyWireguardConfigNode(ring, wg); err != nil {
return err
}
@@ -152,7 +170,7 @@ func (m *Machine) applyWireguardConfig(ring int, wg *wireguard.Config) error {
WithField("subsystem", "wireguard").
WithField("node", m.Name).
WithField("peer", peer.Endpoint.Host).
WithField("ring", ring).
WithField("ring", MustWireguardInterfaceID(ring)).
Print("ignoring unknown endpoint")
case err != nil:
return core.Wrap(err, "peer")
@@ -162,9 +180,9 @@ func (m *Machine) applyWireguardConfig(ring int, wg *wireguard.Config) error {
return nil
}
func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) {
func (m *Machine) getRingInfo(ring rings.RingID) (*RingInfo, bool) {
for _, ri := range m.Rings {
if ri.Ring == ring {
if ri.RingID() == ring {
return ri, ri.Enabled
}
}
@@ -172,13 +190,13 @@ func (m *Machine) getRingInfo(ring int) (*RingInfo, bool) {
return nil, false
}
func (m *Machine) applyRingInfo(ring int, new *RingInfo) error {
func (m *Machine) applyRingInfo(ring rings.RingID, new *RingInfo) error {
cur, _ := m.getRingInfo(ring)
if cur == nil {
// first, append
m.debug().
WithField("node", m.Name).
WithField("ring", ring).
WithField("ring", MustWireguardInterfaceID(ring)).
Print("found")
m.Rings = append(m.Rings, new)
return nil
@@ -188,9 +206,11 @@ func (m *Machine) applyRingInfo(ring int, new *RingInfo) error {
return cur.Merge(new)
}
func (m *Machine) applyWireguardInterfaceConfig(ring int, data wireguard.InterfaceConfig) error {
func (m *Machine) applyWireguardInterfaceConfig(ring rings.RingID,
data wireguard.InterfaceConfig) error {
//
ri := &RingInfo{
Ring: ring,
Ring: MustWireguardInterfaceID(ring),
Enabled: true,
Keys: wireguard.KeyPair{
PrivateKey: data.PrivateKey,
@@ -200,7 +220,9 @@ func (m *Machine) applyWireguardInterfaceConfig(ring int, data wireguard.Interfa
return m.applyRingInfo(ring, ri)
}
func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) error {
func (m *Machine) applyWireguardPeerConfig(ring rings.RingID,
pc wireguard.PeerConfig) error {
//
peer, found := m.getPeerByName(pc.Endpoint.Name())
switch {
case !found:
@@ -212,7 +234,7 @@ func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) er
default:
// apply RingInfo
ri := &RingInfo{
Ring: ring,
Ring: MustWireguardInterfaceID(ring),
Enabled: true,
Keys: wireguard.KeyPair{
PublicKey: pc.PublicKey,
@@ -223,21 +245,29 @@ func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) er
}
}
func (m *Machine) applyZoneNodeID(zoneID, nodeID int) error {
func (m *Machine) applyZoneNodeID(regionID rings.RegionID,
zoneID rings.ZoneID, nodeID rings.NodeID) error {
//
switch {
case zoneID == 0:
case !regionID.Valid():
return fmt.Errorf("invalid %s", "regionID")
case !zoneID.Valid():
return fmt.Errorf("invalid %s", "zoneID")
case nodeID == 0:
case !nodeID.Valid():
return fmt.Errorf("invalid %s", "nodeID")
case m.ID != nodeID:
return fmt.Errorf("invalid %s: %v ≠ %v", "zoneID", m.ID, nodeID)
return fmt.Errorf("invalid %s: %v ≠ %v", "nodeID", 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.zone.ID == 0:
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
return nil
}
}
func (m *Machine) setRingDefaults(ri *RingInfo) error {
@@ -259,8 +289,13 @@ func (m *Machine) setRingDefaults(ri *RingInfo) error {
// RemoveWireguardConfig deletes wgN.conf from the machine's
// config directory.
func (m *Machine) RemoveWireguardConfig(ring int) error {
err := m.RemoveFile("wg%v.conf", ring)
func (m *Machine) RemoveWireguardConfig(ringID rings.RingID) error {
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
return err
}
err = m.RemoveFile(ring.ConfFile())
if os.IsNotExist(err) {
err = nil
}
@@ -268,7 +303,12 @@ func (m *Machine) RemoveWireguardConfig(ring int) error {
return err
}
func (m *Machine) createRingInfo(ring int, enabled bool) (*RingInfo, error) {
func (m *Machine) createRingInfo(ringID rings.RingID, enabled bool) (*RingInfo, error) {
ring, err := AsWireguardInterfaceID(ringID)
if err != nil {
return nil, err
}
keys, err := wireguard.NewKeyPair()
if err != nil {
return nil, err
@@ -280,7 +320,7 @@ func (m *Machine) createRingInfo(ring int, enabled bool) (*RingInfo, error) {
Keys: keys,
}
err = m.applyRingInfo(ring, ri)
err = m.applyRingInfo(ringID, ri)
if err != nil {
return nil, err
}
+8 -6
View File
@@ -9,6 +9,8 @@ import (
"time"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
// LookupNetIP uses the DNS Resolver to get the public addresses associated
@@ -37,8 +39,8 @@ func (m *Machine) init() error {
return core.Wrap(err, m.Name)
}
for i := 0; i < RingsCount; i++ {
if err := m.tryReadWireguardKeys(i); err != nil {
for _, ring := range Rings {
if err := m.tryReadWireguardKeys(ring.ID); err != nil {
return core.Wrap(err, m.Name)
}
}
@@ -65,18 +67,18 @@ func (m *Machine) setID() error {
return err
}
m.ID = int(id)
m.ID = rings.NodeID(id)
return nil
}
// scan is called once we know about all zones and machine names
func (m *Machine) scan(_ *ScanOptions) error {
for i := 0; i < RingsCount; i++ {
if err := m.tryApplyWireguardConfig(i); err != nil {
for _, ring := range Rings {
if err := m.tryApplyWireguardConfig(ring.ID); err != nil {
m.error(err).
WithField("subsystem", "wireguard").
WithField("node", m.Name).
WithField("ring", i).
WithField("ring", MustWireguardInterfaceID(ring.ID)).
Print()
return err
}
+99 -2
View File
@@ -3,6 +3,8 @@ package cluster
import (
"bytes"
"path/filepath"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var (
@@ -24,8 +26,15 @@ type Region struct {
zones []*Zone
Name string
Cluster *string `json:",omitempty" yaml:",omitempty"`
Regions []string `json:",omitempty" yaml:",omitempty"`
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
}
// ForEachRegion calls a function for each Region of the cluster
@@ -92,6 +101,8 @@ func (m *Cluster) initRegions(_ *ScanOptions) error {
}
m.sortRegions()
m.scanRegionID()
m.computeZonesRegion()
return nil
}
@@ -210,6 +221,92 @@ 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]
+105 -109
View File
@@ -4,31 +4,86 @@ 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 int
Ring WireguardInterfaceID
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 {
@@ -54,7 +109,7 @@ func (ri *RingInfo) unsafeMerge(alter *RingInfo) error {
ri.Enabled = true
}
// fill the gaps on our keypair
// fill the gaps on our key pair
if ri.Keys.PrivateKey.IsZero() {
ri.Keys.PrivateKey = alter.Keys.PrivateKey
}
@@ -79,108 +134,34 @@ func canMergeKeyPairs(p1, p2 wireguard.KeyPair) bool {
// RingAddressEncoder provides encoder/decoder access for a particular
// Wireguard ring
type RingAddressEncoder struct {
ID int
ID rings.RingID
Port uint16
Encode func(zoneID, nodeID int) (netip.Addr, bool)
Decode func(addr netip.Addr) (zoneID, nodeID int, ok bool)
Encode func(rings.RegionID, rings.ZoneID, rings.NodeID) (netip.Addr, error)
Decode func(addr netip.Addr) (rings.RegionID, rings.ZoneID, rings.NodeID, bool)
}
var (
// RingZero is a wg0 address encoder/decoder
RingZero = RingAddressEncoder{
ID: 0,
ID: rings.RingZeroID,
Port: RingZeroPort,
Decode: ParseRingZeroAddress,
Encode: RingZeroAddress,
Decode: rings.DecodeRingZeroAddress,
Encode: rings.RingZeroAddress,
}
// RingOne is a wg1 address encoder/decoder
RingOne = RingAddressEncoder{
ID: 1,
ID: rings.RingOneID,
Port: RingOnePort,
Decode: ParseRingOneAddress,
Encode: RingOneAddress,
Decode: rings.DecodeRingOneAddress,
Encode: rings.RingOneAddress,
}
// Rings provides indexed access to the ring address encoders
Rings = [RingsCount]RingAddressEncoder{
Rings = []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 int) 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 int) 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 int, nodeID int, ok bool) {
if addr.IsValid() {
a4 := addr.As4()
if a4[0] == 10 && a4[1] == 0 {
return int(a4[2]), int(a4[3]), true
}
}
return 0, 0, false
}
// RingZeroAddress returns a wg0 IP address
func RingZeroAddress(zoneID, nodeID int) (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 int, nodeID int, ok bool) {
if addr.IsValid() {
a4 := addr.As4()
if a4[0] == 10 && a4[2] == 0 {
zoneID = int(a4[1] >> 4)
nodeID = int(a4[3])
return zoneID, nodeID, true
}
}
return 0, 0, false
}
// RingOneAddress returns a wg1 IP address
func RingOneAddress(zoneID, nodeID int) (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)
@@ -203,14 +184,15 @@ func (r *Ring) AddPeer(p *Machine) bool {
nodeID := p.ID
zoneID := p.Zone()
addr, _ := r.Encode(zoneID, nodeID)
regionID := p.Region()
addr, _ := r.Encode(regionID, zoneID, nodeID)
rp := &RingPeer{
Node: p,
Address: addr,
PrivateKey: ri.Keys.PrivateKey,
PeerConfig: wireguard.PeerConfig{
Name: fmt.Sprintf("%s-%v", p.Name, r.ID),
Name: fmt.Sprintf("%s-%v", p.Name, ri.Ring),
PublicKey: ri.Keys.PublicKey,
Endpoint: wireguard.EndpointAddress{
Host: p.FullName(),
@@ -220,7 +202,7 @@ func (r *Ring) AddPeer(p *Machine) bool {
}
switch {
case r.ID == 0:
case r.ID == rings.RingZeroID:
r.setRingZeroAllowedIPs(rp)
case p.IsGateway():
r.setRingOneGatewayAllowedIPs(rp)
@@ -233,27 +215,27 @@ func (r *Ring) AddPeer(p *Machine) bool {
}
func (r *Ring) setRingZeroAllowedIPs(rp *RingPeer) {
zoneID, _, _ := r.Decode(rp.Address)
regionID, zoneID, _, _ := r.Decode(rp.Address)
// everyone on ring0 is a gateway to ring1
addr, _ := RingOneAddress(zoneID, 0)
rp.AllowCIDR(addr, 12)
subnet, _ := rings.RingOnePrefix(regionID, zoneID)
rp.AllowSubnet(subnet)
// peer
rp.AllowCIDR(rp.Address, 32)
}
func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) {
zoneID, _, _ := r.Decode(rp.Address)
regionID, 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.ID != zoneID {
addr, _ := r.Encode(z.ID, 0)
rp.AllowCIDR(addr, 12)
if !z.Is(regionID, zoneID) {
subnet := z.RingOnePrefix()
rp.AllowSubnet(subnet)
}
return false
})
@@ -262,7 +244,7 @@ func (r *Ring) setRingOneGatewayAllowedIPs(rp *RingPeer) {
r.ForEachZone(func(z *Zone) bool {
z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() {
addr, _ := RingZeroAddress(z.ID, p.ID)
addr, _ := p.RingZeroAddress()
rp.AllowCIDR(addr, 32)
}
return false
@@ -329,15 +311,29 @@ type RingPeer struct {
// AllowCIDR allows an IP range via this peer
func (rp *RingPeer) AllowCIDR(addr netip.Addr, bits int) {
cidr := netip.PrefixFrom(addr, bits)
rp.PeerConfig.AllowedIPs = append(rp.PeerConfig.AllowedIPs, cidr)
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)
}
// NewRing composes a new Ring for Wireguard setup
func NewRing(z ZoneIterator, m MachineIterator, ring int) (*Ring, error) {
r := &Ring{
RingAddressEncoder: Rings[ring],
ZoneIterator: z,
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)
}
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 := 0; ring < RingsCount; ring++ {
err = m.WriteWireguardKeys(ring)
for _, ring := range Rings {
err = m.WriteWireguardKeys(ring.ID)
if err != nil {
return err
}
err = m.SyncWireguardConfig(ring)
err = m.SyncWireguardConfig(ring.ID)
if err != nil {
return err
}
+44 -36
View File
@@ -3,6 +3,8 @@ package cluster
import (
"io/fs"
"os"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var (
@@ -26,22 +28,22 @@ var (
// A WireguardConfigPruner deletes wgN.conf on all machines under
// its scope with the specified ring disabled
type WireguardConfigPruner interface {
PruneWireguardConfig(ring int) error
PruneWireguardConfig(ring rings.RingID) error
}
// PruneWireguardConfig removes wgN.conf files of machines with
// the corresponding ring disabled on all zones
func (m *Cluster) PruneWireguardConfig(ring int) error {
func (m *Cluster) PruneWireguardConfig(ring rings.RingID) error {
return pruneWireguardConfig(m, ring)
}
// PruneWireguardConfig removes wgN.conf files of machines with
// the corresponding ring disabled.
func (z *Zone) PruneWireguardConfig(ring int) error {
func (z *Zone) PruneWireguardConfig(ring rings.RingID) error {
return pruneWireguardConfig(z, ring)
}
func pruneWireguardConfig(m MachineIterator, ring int) error {
func pruneWireguardConfig(m MachineIterator, ring rings.RingID) error {
var err error
m.ForEachMachine(func(p *Machine) bool {
@@ -59,7 +61,7 @@ func pruneWireguardConfig(m MachineIterator, ring int) error {
// PruneWireguardConfig deletes the wgN.conf file if its
// presence on the ring is disabled
func (m *Machine) PruneWireguardConfig(ring int) error {
func (m *Machine) PruneWireguardConfig(ring rings.RingID) error {
_, ok := m.getRingInfo(ring)
if !ok {
return m.RemoveWireguardConfig(ring)
@@ -71,16 +73,16 @@ func (m *Machine) PruneWireguardConfig(ring int) error {
// A WireguardConfigWriter rewrites all wgN.conf on all machines under
// its scope attached to that ring
type WireguardConfigWriter interface {
WriteWireguardConfig(ring int) error
WriteWireguardConfig(ring rings.RingID) error
}
// WriteWireguardConfig rewrites all wgN.conf on all machines
// attached to that ring
func (m *Cluster) WriteWireguardConfig(ring int) error {
func (m *Cluster) WriteWireguardConfig(ring rings.RingID) error {
switch ring {
case 0:
case rings.RingZeroID:
return writeWireguardConfig(m, m, ring)
case 1:
case rings.RingOneID:
var err error
m.ForEachZone(func(z *Zone) bool {
err = writeWireguardConfig(m, z, ring)
@@ -88,24 +90,24 @@ func (m *Cluster) WriteWireguardConfig(ring int) error {
})
return err
default:
return fs.ErrInvalid
return ErrInvalidRing(ring)
}
}
// WriteWireguardConfig rewrites all wgN.conf on all machines
// on the Zone attached to that ring
func (z *Zone) WriteWireguardConfig(ring int) error {
func (z *Zone) WriteWireguardConfig(ring rings.RingID) error {
switch ring {
case 0:
case rings.RingZeroID:
return writeWireguardConfig(z.zones, z.zones, ring)
case 1:
case rings.RingOneID:
return writeWireguardConfig(z.zones, z, ring)
default:
return fs.ErrInvalid
return ErrInvalidRing(ring)
}
}
func writeWireguardConfig(z ZoneIterator, m MachineIterator, ring int) error {
func writeWireguardConfig(z ZoneIterator, m MachineIterator, ring rings.RingID) error {
r, err := NewRing(z, m, ring)
if err != nil {
return err
@@ -121,7 +123,7 @@ func writeWireguardConfig(z ZoneIterator, m MachineIterator, ring int) error {
// WriteWireguardConfig rewrites the wgN.conf file of this Machine
// if enabled
func (m *Machine) WriteWireguardConfig(ring int) error {
func (m *Machine) WriteWireguardConfig(ring rings.RingID) error {
r, err := NewRing(m.zone.zones, m.zone, ring)
if err != nil {
return err
@@ -131,12 +133,17 @@ func (m *Machine) WriteWireguardConfig(ring int) 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("wg%v.conf", r.ID)
f, err := m.CreateTruncFile(ring.ConfFile())
if err != nil {
return err
}
@@ -149,16 +156,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 int) error
SyncWireguardConfig(ring rings.RingID) error
}
// SyncWireguardConfig updates all wgN.conf files for the specified
// ring
func (m *Cluster) SyncWireguardConfig(ring int) error {
func (m *Cluster) SyncWireguardConfig(ring rings.RingID) error {
switch ring {
case 0:
case rings.RingZeroID:
return syncWireguardConfig(m, m, ring)
case 1:
case rings.RingOneID:
var err error
m.ForEachZone(func(z *Zone) bool {
err = syncWireguardConfig(m, z, ring)
@@ -166,24 +173,24 @@ func (m *Cluster) SyncWireguardConfig(ring int) error {
})
return err
default:
return fs.ErrInvalid
return ErrInvalidRing(ring)
}
}
// SyncWireguardConfig updates all wgN.conf files for the specified
// ring
func (z *Zone) SyncWireguardConfig(ring int) error {
func (z *Zone) SyncWireguardConfig(ring rings.RingID) error {
switch ring {
case 0:
case rings.RingZeroID:
return syncWireguardConfig(z.zones, z.zones, ring)
case 1:
case rings.RingOneID:
return syncWireguardConfig(z.zones, z, ring)
default:
return fs.ErrInvalid
return ErrInvalidRing(ring)
}
}
func syncWireguardConfig(z ZoneIterator, m MachineIterator, ring int) error {
func syncWireguardConfig(z ZoneIterator, m MachineIterator, ring rings.RingID) error {
r, err := NewRing(z, m, ring)
if err != nil {
return err
@@ -203,27 +210,27 @@ func syncWireguardConfig(z ZoneIterator, m MachineIterator, ring int) error {
// SyncWireguardConfig updates all wgN.conf files for the specified
// ring
func (m *Machine) SyncWireguardConfig(ring int) error {
func (m *Machine) SyncWireguardConfig(ring rings.RingID) 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 int) error
WriteWireguardKeys(ring rings.RingID) error
}
// WriteWireguardKeys rewrites all wgN.{key,pub} files
func (m *Cluster) WriteWireguardKeys(ring int) error {
func (m *Cluster) WriteWireguardKeys(ring rings.RingID) error {
return writeWireguardKeys(m, ring)
}
// WriteWireguardKeys rewrites all wgN.{key,pub} files on this zone
func (z *Zone) WriteWireguardKeys(ring int) error {
func (z *Zone) WriteWireguardKeys(ring rings.RingID) error {
return writeWireguardKeys(z, ring)
}
func writeWireguardKeys(m MachineIterator, ring int) error {
func writeWireguardKeys(m MachineIterator, ring rings.RingID) error {
var err error
m.ForEachMachine(func(p *Machine) bool {
@@ -240,12 +247,12 @@ func writeWireguardKeys(m MachineIterator, ring int) error {
}
// WriteWireguardKeys writes the wgN.key/wgN.pub files
func (m *Machine) WriteWireguardKeys(ring int) error {
func (m *Machine) WriteWireguardKeys(ringID rings.RingID) error {
var err error
var key, pub string
var ri *RingInfo
ri, _ = m.getRingInfo(ring)
ri, _ = m.getRingInfo(ringID)
if ri != nil {
key = ri.Keys.PrivateKey.String()
pub = ri.Keys.PublicKey.String()
@@ -258,12 +265,13 @@ func (m *Machine) WriteWireguardKeys(ring int) error {
pub = ri.Keys.PrivateKey.Public().String()
}
err = m.WriteStringFile(key+"\n", "wg%v.key", ring)
keyFile, pubFile, _ := ri.Ring.Files()
err = m.WriteStringFile(key+"\n", keyFile)
if err != nil {
return err
}
err = m.WriteStringFile(pub+"\n", "wg%v.pub", ring)
err = m.WriteStringFile(pub+"\n", pubFile)
if err != nil {
return err
}
+42 -4
View File
@@ -2,6 +2,8 @@ package cluster
import (
"io/fs"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var (
@@ -17,9 +19,10 @@ type ZoneIterator interface {
// affinity.
type Zone struct {
zones *Cluster
region *Region
logger `json:"-" yaml:"-"`
ID int
ID rings.ZoneID
Name string
Regions []string `json:",omitempty" yaml:",omitempty"`
@@ -31,7 +34,7 @@ func (z *Zone) String() string {
}
// SetGateway configures a machine to be the zone's ring0 gateway
func (z *Zone) SetGateway(gatewayID int, enabled bool) error {
func (z *Zone) SetGateway(gatewayID rings.NodeID, enabled bool) error {
var err error
var found bool
@@ -56,8 +59,8 @@ func (z *Zone) SetGateway(gatewayID int, enabled bool) error {
}
// GatewayIDs returns the list of IDs of machines that act as ring0 gateways
func (z *Zone) GatewayIDs() ([]int, int) {
var out []int
func (z *Zone) GatewayIDs() ([]rings.NodeID, int) {
var out []rings.NodeID
z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() {
out = append(out, p.ID)
@@ -67,3 +70,38 @@ func (z *Zone) GatewayIDs() ([]int, 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
@@ -0,0 +1,122 @@
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
@@ -0,0 +1,53 @@
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)
}
}
}
+19 -10
View File
@@ -14,7 +14,7 @@ func RingZeroPrefix(region RegionID, zone ZoneID) (cidr netip.Prefix, err error)
case !zone.Valid():
err = ErrOutOfRange(zone, "zone")
default:
addr := unsafeRingZeroAddress(region, zone, 0)
addr := UnsafeRingZeroAddress(region, zone, 0)
cidr = netip.PrefixFrom(addr, RingZeroBits)
}
@@ -33,7 +33,7 @@ func RingZeroAddress(region RegionID, zone ZoneID, node NodeID) (addr netip.Addr
case !node.ValidZero():
err = ErrOutOfRange(node, "node")
default:
addr = unsafeRingZeroAddress(region, zone, node)
addr = UnsafeRingZeroAddress(region, zone, node)
}
return addr, err
@@ -52,7 +52,7 @@ func RingOnePrefix(region RegionID, zone ZoneID) (cidr netip.Prefix, err error)
case !zone.Valid():
err = ErrOutOfRange(zone, "zone")
default:
addr := unsafeRingOneAddress(region, zone, 0)
addr := UnsafeRingOneAddress(region, zone, 0)
cidr = netip.PrefixFrom(addr, RingOneBits)
}
return cidr, err
@@ -71,7 +71,7 @@ func RingOneAddress(region RegionID, zone ZoneID, node NodeID) (addr netip.Addr,
case !node.Valid():
err = ErrOutOfRange(node, "node")
default:
addr = unsafeRingOneAddress(region, zone, node)
addr = UnsafeRingOneAddress(region, zone, node)
}
return addr, err
}
@@ -86,7 +86,7 @@ func RingTwoPrefix(region RegionID) (cidr netip.Prefix, err error) {
case !region.Valid():
err = ErrOutOfRange(region, "region")
default:
addr := unsafeRingTwoAddress(region, 0)
addr := UnsafeRingTwoAddress(region, 0)
cidr = netip.PrefixFrom(addr, RingTwoBits)
}
return cidr, err
@@ -101,20 +101,25 @@ func RingThreePrefix(region RegionID) (subnet netip.Prefix, err error) {
case !region.Valid():
err = ErrOutOfRange(region, "region")
default:
addr := unsafeRingThreeAddress(region, 0)
addr := UnsafeRingThreeAddress(region, 0)
subnet = netip.PrefixFrom(addr, RingThreeBits)
}
return subnet, err
}
func unsafeRingZeroAddress(region RegionID, zone ZoneID, node NodeID) netip.Addr {
// UnsafeRingZeroAddress is equivalent ot RingZeroAddress but without validating
// the input.
func UnsafeRingZeroAddress(region RegionID, zone ZoneID, node NodeID) netip.Addr {
r := uint(region)
z := uint(zone)
n := uint(node)
return AddrFrom4(10, 0, r<<4+z, n)
}
func unsafeRingOneAddress(region RegionID, zone ZoneID, node NodeID) netip.Addr {
// UnsafeRingOneAddress is equivalent ot RingOneAddress but without validating
// the input.
func UnsafeRingOneAddress(region RegionID, zone ZoneID, node NodeID) netip.Addr {
r := uint(region)
z := uint(zone)
n := uint(node)
@@ -125,7 +130,9 @@ func unsafeRingOneAddress(region RegionID, zone ZoneID, node NodeID) netip.Addr
return AddrFrom4(10, r, z<<4+n1, n0)
}
func unsafeRingTwoAddress(region RegionID, n uint) netip.Addr {
// UnsafeRingTwoAddress is equivalent ot RingTwoAddress but without validating
// the input.
func UnsafeRingTwoAddress(region RegionID, n uint) netip.Addr {
r := uint(region)
n1 := n >> 8
@@ -134,7 +141,9 @@ func unsafeRingTwoAddress(region RegionID, n uint) netip.Addr {
return AddrFrom4(10, r, n1, n0)
}
func unsafeRingThreeAddress(region RegionID, n uint) netip.Addr {
// UnsafeRingThreeAddress is equivalent ot RingThreeAddress but without validating
// the input.
func UnsafeRingThreeAddress(region RegionID, n uint) netip.Addr {
r := uint(region)
n2 := n >> 16
+58 -2
View File
@@ -3,21 +3,34 @@
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) - 1
NodeMax = (1 << 12) - 2
// NodeZeroMax indicates the highest number that can be used for a [NodeID]
// when its a gateway connected to Ring 0 (backbone).
NodeZeroMax = (1 << 8) - 1
NodeZeroMax = (1 << 8) - 2
// RingZeroBits indicates the size of the prefix on the ring 0 (backbone) network.
RingZeroBits = 16
@@ -31,18 +44,41 @@ 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
@@ -53,8 +89,28 @@ 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))
}
}
+72
View File
@@ -0,0 +1,72 @@
package tools
import (
"bytes"
"fmt"
"io"
)
// LazyBuffer is a [bytes.Buffer] that minimizes counting and error checks.
type LazyBuffer bytes.Buffer
// Sys returns the underlying [bytes.Buffer].
func (buf *LazyBuffer) Sys() *bytes.Buffer {
if buf == nil {
return nil
}
return (*bytes.Buffer)(buf)
}
// Len tells the size in bytes of the currently stored data.
func (buf *LazyBuffer) Len() int { return buf.Sys().Len() }
// String returns the stored data as string.
func (buf *LazyBuffer) String() string { return buf.Sys().String() }
// Bytes returns the stored data as a bytes slice.
func (buf *LazyBuffer) Bytes() []byte { return buf.Sys().Bytes() }
// Write implements the standard io.Writer interface.
func (buf *LazyBuffer) Write(b []byte) (int, error) { return buf.Sys().Write(b) }
// WriteTo implements the standard WriteTo() interface.
func (buf *LazyBuffer) WriteTo(out io.Writer) (int64, error) { return buf.Sys().WriteTo(out) }
// Print appends the [fmt.Print] equivalent to the buffer.
func (buf *LazyBuffer) Print(a ...any) error {
_, err := fmt.Fprint(buf.Sys(), a...)
return err
}
// Println appends the [fmt.Println] equivalent to the buffer.
func (buf *LazyBuffer) Println(a ...any) error {
_, err := fmt.Fprintln(buf.Sys(), a...)
return err
}
// Printf appends the [fmt.Printf] equivalent to the buffer.
func (buf *LazyBuffer) Printf(format string, a ...any) error {
_, err := fmt.Fprintf(buf.Sys(), format, a...)
return err
}
// WriteRunes appends the given runes as UTF-8 characters to the buffer.
func (buf *LazyBuffer) WriteRunes(runes ...rune) {
for _, r := range runes {
_, _ = buf.Sys().WriteRune(r)
}
}
// WriteBytes writes the given byte arrays to the buffer.
func (buf *LazyBuffer) WriteBytes(s ...[]byte) {
for _, b := range s {
_, _ = buf.Sys().Write(b)
}
}
// WriteStrings writes the given strings as UTF-8 to the buffer.
func (buf *LazyBuffer) WriteStrings(strings ...string) {
for _, s := range strings {
_, _ = buf.Sys().WriteString(s)
}
}
+1 -2
View File
@@ -5,8 +5,7 @@ set -eu
: ${GO:=go}
MODULES=$(find * -name go.mod -exec dirname '{}' \;)
GROUPS="pkg cmd"
BASE="$PWD"
GROUPS=""
mod() {
local d="${1:-.}"
+102 -40
View File
@@ -7,6 +7,12 @@ INDEX="$1"
PROJECTS="$(cut -d':' -f1 "$INDEX")"
COMMANDS="tidy get build test up"
TAB=$(printf "\t")
escape_dir() {
echo "$1" | sed -e 's|/|\\/|g' -e 's|\.|\\.|g'
}
expand() {
local prefix="$1" suffix="$2"
local x= out=
@@ -41,12 +47,6 @@ packed_oneline() {
packed | tr '\n' ';' | sed -e 's|;$||' -e 's|then;|then |g' -e 's|;[ \t]*|; |g'
}
gen_install_tools() {
cat <<EOT
for url in \$(GO_INSTALL_URLS); do \$(GO) install -v \$\$url; done
EOT
}
gen_revive_exclude() {
local self="$1"
local dirs= d=
@@ -61,36 +61,71 @@ gen_revive_exclude() {
done
}
for cmd in $COMMANDS; do
all="$(prefixed $cmd $PROJECTS)"
depsx=
gen_var_name() {
local x=
for x; do
echo "$x" | tr 'a-z-' 'A-Z_'
done
}
# generate files lists
#
gen_files_lists() {
local name= dir= mod= deps=
local files= files_cmd=
local filter= out_pat=
cat <<EOT
.PHONY: $cmd $all
$cmd: $all
GO_FILES = \$(shell find * \\
-type d -name node_modules -prune -o \\
-type f -name '*.go' -print )
EOT
while IFS=: read name dir mod deps; do
files=GO_FILES_$(gen_var_name "$name")
filter="-e '/^\.$/d;'"
[ "x$dir" = "x." ] || filter="$filter -e '/^$(escape_dir "$dir")$/d;'"
out_pat="$(cut -d: -f2 "$INDEX" | eval "sed $filter -e 's|$|/%|'" | tr '\n' ' ' | sed -e 's| \+$||')"
if [ "x$dir" = "x." ]; then
# root
files_cmd="\$(GO_FILES)"
files_cmd="\$(filter-out $out_pat, $files_cmd)"
else
files_cmd="\$(filter $dir/%, \$(GO_FILES))"
files_cmd="\$(filter-out $out_pat, $files_cmd)"
files_cmd="\$(patsubst $dir/%,%,$files_cmd)"
fi
cat <<-EOT
$files$TAB=$TAB$files_cmd
EOT
done < "$INDEX" | column -t -s "$TAB" -o " "
}
gen_make_targets() {
local cmd="$1" name="$2" dir="$3" mod="$4" deps="$5"
local call= callu= callx=
local depsx= cmdx=
local sequential=
# default calls
case "$cmd" in
tidy)
call="$(cat <<-EOT | packed
\$(GO) mod tidy
# unconditional
callu="\$(GO) mod tidy"
# go vet and revive only if there are .go files
#
$(cat <<-EOL | packed_oneline
set -e
FILES="\$\$(\$(GO) list -f '{{len .GoFiles}}' ./...)"
if [ -n "\$\$FILES" ]; then
\$(GO) vet ./...
\$(REVIVE) \$(REVIVE_RUN_ARGS) ./...
fi
EOL
)
call="$(cat <<-EOT | packed
\$(GO) vet ./...
\$(GOLANGCI_LINT) run
\$(REVIVE) \$(REVIVE_RUN_ARGS) ./...
EOT
)"
depsx="fmt \$(REVIVE)"
depsx="fmt"
;;
up)
call="\$(GO) get -u -v ./...
@@ -111,10 +146,6 @@ EOT
sequential=false ;;
esac
while IFS=: read name dir mod deps; do
deps=$(echo "$deps" | tr ',' ' ')
# cd $dir
if [ "." = "$dir" ]; then
# root
@@ -123,7 +154,6 @@ EOT
cd="cd '$dir'; "
fi
callx="$call"
if [ "$name" = root ]; then
# special case
case "$cmd" in
@@ -140,17 +170,20 @@ EOT
[ -z "$cmdx" ] || cmdx="\$(GO) $cmdx -v ./..."
if [ "up" = "$cmd" ]; then
case "$cmd" in
up)
callx="$cmdx
\$(GO) mod tidy
$(gen_install_tools)"
elif [ "get" = "$cmd" ]; then
callx="$cmdx
$(gen_install_tools)"
elif [ -n "$cmdx" ]; then
classx="$cmdx"
fi
\$(GO) mod tidy"
;;
get)
callx="$cmdx"
;;
*)
callx="$call"
;;
esac
else
callx="$call"
fi
if [ "build" = "$cmd" ]; then
@@ -181,16 +214,45 @@ $(gen_install_tools)"
deps=
fi
files=GO_FILES_$(gen_var_name "$name")
cat <<EOT
$cmd-$name:${deps:+ $(prefixed $cmd $deps)}${depsx:+ | $depsx} ; \$(info \$(M) $cmd: $name)
$(echo "$callx" | sed -e "/^$/d;" -e "s|^|\t\$(Q) $cd|")
$cmd-$name:${deps:+ $(prefixed $cmd $deps)}${depsx:+ | $depsx} ; \$(info \$(M) $cmd: $name)
EOT
if [ -n "$callu" ]; then
# unconditionally
echo "$callu" | sed -e "/^$/d;" -e "s|^|\t\$(Q) $cd|"
fi
if [ -n "$callx" ]; then
# only if there are files
echo "ifneq (\$($files),)"
echo "$callx" | sed -e "/^$/d;" -e "s|^|\t\$(Q) $cd|"
echo "endif"
fi
}
gen_files_lists
for cmd in $COMMANDS; do
all="$(prefixed $cmd $PROJECTS)"
depsx=
cat <<EOT
.PHONY: $cmd $all
$cmd: $all
EOT
while IFS=: read name dir mod deps; do
deps=$(echo "$deps" | tr ',' ' ')
gen_make_targets "$cmd" "$name" "$dir" "$mod" "$deps"
done < "$INDEX"
done
for x in $PROJECTS; do
cat <<EOT
$x: $(suffixed $x get build tidy)
EOT
done
+1 -1
View File
@@ -17,7 +17,7 @@ enableAllRules = true
[rule.cyclomatic]
arguments = [10]
[rule.line-length-limit]
arguments = [100]
arguments = [120]
severity = "warning"
[rule.comment-spacings]
severity = "warning"
+9 -5
View File
@@ -1,7 +1,11 @@
//go:build tools
// Package tools contains helpers
package tools
import (
_ "github.com/mgechev/revive"
)
import "io"
// LazyClose closes an [io.Closer] and discards the error
func LazyClose(p io.Closer) {
if p != nil {
_ = p.Close()
}
}
-2
View File
@@ -8,8 +8,6 @@ import (
"darvaza.org/core"
)
type sectionHandler func(*Config, *basic.Section) error
var sectionMap = map[string]func(*Config, *basic.Section) error{
"Interface": loadInterfaceConfSection,
"Peer": loadPeerConfSection,