Compare commits
66 Commits
dev-amery-
...
main
41 changed files with 2116 additions and 541 deletions
@ -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\n", cidr, m.Name) |
||||
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) |
||||
} |
@ -0,0 +1,31 @@
|
||||
package main |
||||
|
||||
import ( |
||||
_ "embed" |
||||
|
||||
"fmt" |
||||
"os" |
||||
|
||||
"github.com/spf13/cobra" |
||||
) |
||||
|
||||
//go:generate sh -c "git describe | tr -d '\r\n' > .version"
|
||||
//go:embed .version
|
||||
var version string |
||||
|
||||
var versionCmd = &cobra.Command{ |
||||
Use: "version", |
||||
Short: "Returns jpictl's version", |
||||
Args: cobra.NoArgs, |
||||
Run: func(_ *cobra.Command, _ []string) { |
||||
_, _ = fmt.Fprintf(os.Stdout, "%s\n", version) |
||||
}, |
||||
} |
||||
|
||||
func init() { |
||||
if version == "" { |
||||
version = "undetermined" |
||||
} |
||||
|
||||
rootCmd.AddCommand(versionCmd) |
||||
} |
@ -1,54 +1,43 @@
|
||||
module git.jpi.io/amery/jpictl |
||||
|
||||
go 1.19 |
||||
|
||||
replace asciigoat.org/ini => ../../../asciigoat.org/ini |
||||
go 1.21 |
||||
|
||||
require ( |
||||
asciigoat.org/core v0.3.9 // indirect |
||||
asciigoat.org/ini v0.2.5 |
||||
darvaza.org/core v0.12.0 |
||||
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 |
||||
github.com/gofrs/uuid/v5 v5.0.0 |
||||
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.0 |
||||
github.com/libdns/libdns v0.2.1 |
||||
github.com/mgechev/revive v1.3.7 |
||||
github.com/libdns/cloudflare v0.1.1 |
||||
github.com/libdns/libdns v0.2.2 |
||||
github.com/spf13/cobra v1.8.0 |
||||
golang.org/x/crypto v0.20.0 |
||||
golang.org/x/net v0.21.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.3.2 // indirect |
||||
github.com/chavacava/garif v0.1.0 // indirect |
||||
github.com/fatih/color v1.16.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.58 // 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/miekg/dns v1.1.59 // indirect |
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect |
||||
github.com/rs/zerolog v1.32.0 // indirect |
||||
github.com/spf13/afero v1.11.0 // indirect |
||||
github.com/rs/zerolog v1.33.0 // indirect |
||||
github.com/spf13/pflag v1.0.5 // indirect |
||||
golang.org/x/mod v0.15.0 // indirect |
||||
golang.org/x/sync v0.6.0 // indirect |
||||
golang.org/x/sys v0.17.0 // indirect |
||||
golang.org/x/text v0.14.0 // indirect |
||||
golang.org/x/tools v0.18.0 // indirect |
||||
golang.org/x/mod v0.17.0 // indirect |
||||
golang.org/x/sync v0.7.0 // indirect |
||||
golang.org/x/sys v0.22.0 // indirect |
||||
golang.org/x/text v0.16.0 // indirect |
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect |
||||
) |
||||
|
@ -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 |
||||
} |
@ -0,0 +1,77 @@
|
||||
package rings |
||||
|
||||
import "net/netip" |
||||
|
||||
// AddrFromU32 converts a 32bit value into an IPv4
|
||||
// address.
|
||||
func AddrFromU32(v uint32) netip.Addr { |
||||
return AddrFrom4( |
||||
uint(v>>24), |
||||
uint(v>>16), |
||||
uint(v>>8), |
||||
uint(v), |
||||
) |
||||
} |
||||
|
||||
// AddrFrom4 assembles an IPv4 address for 4 numbers.
|
||||
// each number is truncated to 8-bits.
|
||||
func AddrFrom4(a, b, c, d uint) netip.Addr { |
||||
return netip.AddrFrom4([4]byte{ |
||||
byte(a & 0xff), |
||||
byte(b & 0xff), |
||||
byte(c & 0xff), |
||||
byte(d & 0xff), |
||||
}) |
||||
} |
||||
|
||||
// AddrToU32 converts a valid IPv4 address into it's
|
||||
// 32bit numeric representation.
|
||||
func AddrToU32(addr netip.Addr) (v uint32, ok bool) { |
||||
if addr.IsValid() { |
||||
if addr.Is4() || addr.Is4In6() { |
||||
a4 := addr.As4() |
||||
|
||||
v = uint32(a4[0])<<24 + |
||||
uint32(a4[1])<<16 + |
||||
uint32(a4[2])<<8 + |
||||
uint32(a4[3]) |
||||
return v, true |
||||
} |
||||
} |
||||
|
||||
return 0, false |
||||
} |
||||
|
||||
// PrefixToRange returns the beginning and end of a
|
||||
// [netip.Prefix] (aka CIDR or subnet).
|
||||
func PrefixToRange(subnet netip.Prefix) (from, to netip.Addr, ok bool) { |
||||
var u uint32 |
||||
|
||||
addr := subnet.Addr() |
||||
if u, ok = AddrToU32(addr); ok { |
||||
bits := subnet.Bits() |
||||
|
||||
switch { |
||||
case bits > 32, bits < 0: |
||||
// bad
|
||||
case bits == 32: |
||||
// single
|
||||
from, to, ok = addr, addr, true |
||||
default: |
||||
// subnet
|
||||
shift := 32 - bits |
||||
|
||||
m1 := uint32((1 << shift) - 1) |
||||
m0 := uint32(0xffffffff) & ^m1 |
||||
|
||||
u0 := u & m0 |
||||
u1 := u0 + m1 |
||||
|
||||
ok = true |
||||
from = AddrFromU32(u0) |
||||
to = AddrFromU32(u1) |
||||
} |
||||
} |
||||
|
||||
return from, to, ok |
||||
} |
@ -0,0 +1,178 @@
|
||||
package rings |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/netip" |
||||
"testing" |
||||
) |
||||
|
||||
func TestAddrFrom4(t *testing.T) { |
||||
cases := []struct { |
||||
v [4]uint |
||||
s string |
||||
}{ |
||||
{[4]uint{0, 0, 0, 0}, "0.0.0.0"}, |
||||
{[4]uint{127, 0, 0, 1}, "127.0.0.1"}, |
||||
{[4]uint{4096 + 127, 0, 0, 1}, "127.0.0.1"}, |
||||
{[4]uint{257, 258, 259, 260}, "1.2.3.4"}, |
||||
{[4]uint{255, 255, 255, 255}, "255.255.255.255"}, |
||||
} |
||||
|
||||
for i, tc := range cases { |
||||
fn := fmt.Sprintf("%v.%v.%v.%v", tc.v[0], tc.v[1], tc.v[2], tc.v[3]) |
||||
addr := AddrFrom4(tc.v[0], tc.v[1], tc.v[2], tc.v[3]) |
||||
s := addr.String() |
||||
|
||||
if s == tc.s { |
||||
t.Logf("[%v/%v]: %s → %s", i, len(cases), fn, s) |
||||
} else { |
||||
t.Errorf("ERROR: [%v/%v]: %s → %s (expected %s)", i, len(cases), fn, s, tc.s) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestAddrU32Invalid(t *testing.T) { |
||||
cases := []netip.Addr{ |
||||
{}, |
||||
netip.IPv6Unspecified(), |
||||
netip.IPv6Loopback(), |
||||
} |
||||
|
||||
for i, tc := range cases { |
||||
v, ok := AddrToU32(tc) |
||||
switch { |
||||
case !ok && v == 0: |
||||
t.Logf("[%v/%v]: %s → %v %v", i, len(cases), tc, 0, false) |
||||
default: |
||||
t.Errorf("ERROR: [%v/%v]: %s → %v %v (expected %v %v)", i, len(cases), |
||||
tc, v, ok, 0, false) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestAddrU32Valid(t *testing.T) { |
||||
cases := []netip.Addr{ |
||||
netip.IPv4Unspecified(), |
||||
AddrFrom4(0, 0, 0, 0), |
||||
AddrFrom4(1, 2, 3, 4), |
||||
AddrFrom4(10, 20, 30, 40), |
||||
AddrFrom4(127, 0, 0, 1), |
||||
AddrFrom4(255, 255, 255, 255), |
||||
MustParseAddr("::ffff:1.2.3.4"), |
||||
} |
||||
|
||||
for i, tc := range cases { |
||||
u32, ok := AddrToU32(tc) |
||||
if !ok { |
||||
t.Errorf("ERROR: [%v/%v]: %s → %v %v", i, len(cases), tc, u32, ok) |
||||
continue |
||||
} |
||||
|
||||
addr := AddrFromU32(u32) |
||||
if tc.Is4In6() { |
||||
ok = addr.Compare(tc.Unmap()) == 0 |
||||
} else { |
||||
ok = addr.Compare(tc) == 0 |
||||
} |
||||
|
||||
if ok { |
||||
t.Logf("[%v/%v]: %s → %v → %s", i, len(cases), tc, u32, addr) |
||||
} else { |
||||
t.Errorf("ERROR: [%v/%v]: %s → %v → %s", i, len(cases), tc, u32, addr) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func MustParseAddr(s string) netip.Addr { |
||||
addr, err := netip.ParseAddr(s) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return addr |
||||
} |
||||
|
||||
func MustParsePrefix(s string) netip.Prefix { |
||||
subnet, err := netip.ParsePrefix(s) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return subnet |
||||
} |
||||
|
||||
func TestPrefixToRangeValid(t *testing.T) { |
||||
cases := []struct { |
||||
subnet netip.Prefix |
||||
from netip.Addr |
||||
to netip.Addr |
||||
}{ |
||||
{ |
||||
MustParsePrefix("127.0.0.1/32"), |
||||
MustParseAddr("127.0.0.1"), |
||||
MustParseAddr("127.0.0.1"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("127.0.0.1/24"), |
||||
MustParseAddr("127.0.0.0"), |
||||
MustParseAddr("127.0.0.255"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("127.0.1.2/16"), |
||||
MustParseAddr("127.0.0.0"), |
||||
MustParseAddr("127.0.255.255"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("127.1.2.3/8"), |
||||
MustParseAddr("127.0.0.0"), |
||||
MustParseAddr("127.255.255.255"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("10.20.30.40/12"), |
||||
MustParseAddr("10.16.0.0"), |
||||
MustParseAddr("10.31.255.255"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("10.20.30.40/20"), |
||||
MustParseAddr("10.20.16.0"), |
||||
MustParseAddr("10.20.31.255"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("10.0.0.0/12"), |
||||
MustParseAddr("10.0.0.0"), |
||||
MustParseAddr("10.15.255.255"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("10.16.0.0/12"), |
||||
MustParseAddr("10.16.0.0"), |
||||
MustParseAddr("10.31.255.255"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("10.32.0.0/12"), |
||||
MustParseAddr("10.32.0.0"), |
||||
MustParseAddr("10.47.255.255"), |
||||
}, |
||||
{ |
||||
MustParsePrefix("10.48.0.0/12"), |
||||
MustParseAddr("10.48.0.0"), |
||||
MustParseAddr("10.63.255.255"), |
||||
}, |
||||
} |
||||
|
||||
for i, tc := range cases { |
||||
from, to, ok := PrefixToRange(tc.subnet) |
||||
if ok && from.IsValid() && to.IsValid() && |
||||
from.Compare(tc.from) == 0 && |
||||
to.Compare(tc.to) == 0 { |
||||
//
|
||||
t.Logf("[%v/%v]: %s → %s - %s", |
||||
i, len(cases), |
||||
tc.subnet, |
||||
from, to) |
||||
} else { |
||||
t.Errorf("ERROR: [%v/%v]: %q → %s - %s %v (expected %s - %s %v)", |
||||
i, len(cases), |
||||
tc.subnet, |
||||
from, to, ok, |
||||
tc.from, tc.to, true) |
||||
} |
||||
} |
||||
} |
@ -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 |
||||
} |
@ -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) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,154 @@
|
||||
package rings |
||||
|
||||
import "net/netip" |
||||
|
||||
// RingZeroPrefix represents the backbone that connects gateways
|
||||
// of the different Ring 1 networks.
|
||||
//
|
||||
// The ring 0 network corresponds to what would be ring 2 for region_id 0.
|
||||
// 10.0.0.0-10.0.255.255
|
||||
func RingZeroPrefix(region RegionID, zone ZoneID) (cidr netip.Prefix, err error) { |
||||
switch { |
||||
case !region.Valid(): |
||||
err = ErrOutOfRange(region, "region") |
||||
case !zone.Valid(): |
||||
err = ErrOutOfRange(zone, "zone") |
||||
default: |
||||
addr := UnsafeRingZeroAddress(region, zone, 0) |
||||
cidr = netip.PrefixFrom(addr, RingZeroBits) |
||||
} |
||||
|
||||
return cidr, err |
||||
} |
||||
|
||||
// RingZeroAddress returns a Ring 0 address for a particular node.
|
||||
//
|
||||
// A ring 0 address looks like 10.0.(region_id << 4 + zone_id).(node_id)/20
|
||||
func RingZeroAddress(region RegionID, zone ZoneID, node NodeID) (addr netip.Addr, err error) { |
||||
switch { |
||||
case !region.Valid(): |
||||
err = ErrOutOfRange(region, "region") |
||||
case !zone.Valid(): |
||||
err = ErrOutOfRange(zone, "zone") |
||||
case !node.ValidZero(): |
||||
err = ErrOutOfRange(node, "node") |
||||
default: |
||||
addr = UnsafeRingZeroAddress(region, zone, node) |
||||
} |
||||
|
||||
return addr, err |
||||
} |
||||
|
||||
// RingOnePrefix represents a (virtual) local network of a zone.
|
||||
//
|
||||
// Ring 1 is `10.(region_id).(zone_id << 4).(node_id)/20` network
|
||||
// grouped under what would be Ring 2 for region_id 0.
|
||||
// There are 12 bits worth of nodes but nodes under 255 are special
|
||||
// as they also get a slot on Ring 0.
|
||||
func RingOnePrefix(region RegionID, zone ZoneID) (cidr netip.Prefix, err error) { |
||||
switch { |
||||
case !region.Valid(): |
||||
err = ErrOutOfRange(region, "region") |
||||
case !zone.Valid(): |
||||
err = ErrOutOfRange(zone, "zone") |
||||
default: |
||||
addr := UnsafeRingOneAddress(region, zone, 0) |
||||
cidr = netip.PrefixFrom(addr, RingOneBits) |
||||
} |
||||
return cidr, err |
||||
} |
||||
|
||||
// RingOneAddress returns a Ring 1 address for a particular node.
|
||||
//
|
||||
// A ring 1 address is `10.(region_id).(zone_id << 4).(node_id)/20`
|
||||
// but the node_id can take up to 12 bits.
|
||||
func RingOneAddress(region RegionID, zone ZoneID, node NodeID) (addr netip.Addr, err error) { |
||||
switch { |
||||
case !region.Valid(): |
||||
err = ErrOutOfRange(region, "region") |
||||
case !zone.Valid(): |
||||
err = ErrOutOfRange(zone, "zone") |
||||
case !node.Valid(): |
||||
err = ErrOutOfRange(node, "node") |
||||
default: |
||||
addr = UnsafeRingOneAddress(region, zone, node) |
||||
} |
||||
return addr, err |
||||
} |
||||
|
||||
// RingTwoPrefix represents the services of a cluster
|
||||
//
|
||||
// Ring 2 subnets are of the form `10.(region_id).0.0/20`,
|
||||
// using the address space that would belong to the ring 3
|
||||
// region_id 0.
|
||||
func RingTwoPrefix(region RegionID) (cidr netip.Prefix, err error) { |
||||
switch { |
||||
case !region.Valid(): |
||||
err = ErrOutOfRange(region, "region") |
||||
default: |
||||
addr := UnsafeRingTwoAddress(region, 0) |
||||
cidr = netip.PrefixFrom(addr, RingTwoBits) |
||||
} |
||||
return cidr, err |
||||
} |
||||
|
||||
// RingThreePrefix returns the subnet corresponding to
|
||||
// the pods of a cluster.
|
||||
//
|
||||
// Ring 3 is a `10.(region_id << 4).0.0/12` network
|
||||
func RingThreePrefix(region RegionID) (subnet netip.Prefix, err error) { |
||||
switch { |
||||
case !region.Valid(): |
||||
err = ErrOutOfRange(region, "region") |
||||
default: |
||||
addr := UnsafeRingThreeAddress(region, 0) |
||||
subnet = netip.PrefixFrom(addr, RingThreeBits) |
||||
} |
||||
return subnet, err |
||||
} |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
// 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) |
||||
|
||||
n1 := n >> 8 |
||||
n0 := n >> 0 |
||||
|
||||
return AddrFrom4(10, r, z<<4+n1, n0) |
||||
} |
||||
|
||||
// UnsafeRingTwoAddress is equivalent ot RingTwoAddress but without validating
|
||||
// the input.
|
||||
func UnsafeRingTwoAddress(region RegionID, n uint) netip.Addr { |
||||
r := uint(region) |
||||
|
||||
n1 := n >> 8 |
||||
n0 := n >> 0 |
||||
|
||||
return AddrFrom4(10, r, n1, n0) |
||||
} |
||||
|
||||
// UnsafeRingThreeAddress is equivalent ot RingThreeAddress but without validating
|
||||
// the input.
|
||||
func UnsafeRingThreeAddress(region RegionID, n uint) netip.Addr { |
||||
r := uint(region) |
||||
|
||||
n2 := n >> 16 |
||||
n1 := n >> 8 |
||||
n0 := n >> 0 |
||||
|
||||
return AddrFrom4(10, r<<4+n2, n1, n0) |
||||
} |
@ -0,0 +1,63 @@
|
||||
package rings |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/netip" |
||||
"testing" |
||||
) |
||||
|
||||
func TestRingZeroAddress(t *testing.T) { |
||||
RZNTest(t, "RingZeroAddress", RingZeroAddress, []RZNTestCase{ |
||||
{1, 1, 50, MustParseAddr("10.0.17.50")}, |
||||
{1, 2, 50, MustParseAddr("10.0.18.50")}, |
||||
{2, 3, 1, MustParseAddr("10.0.35.1")}, |
||||
{2, 3, 300, netip.Addr{}}, |
||||
}) |
||||
} |
||||
|
||||
func TestRingOneAddress(t *testing.T) { |
||||
RZNTest(t, "RingOneAddress", RingOneAddress, []RZNTestCase{ |
||||
{1, 1, 50, MustParseAddr("10.1.16.50")}, |
||||
{1, 2, 50, MustParseAddr("10.1.32.50")}, |
||||
{2, 3, 300, MustParseAddr("10.2.49.44")}, |
||||
{1, 20, 50, netip.Addr{}}, |
||||
}) |
||||
} |
||||
|
||||
type RZNTestCase struct { |
||||
region RegionID |
||||
zone ZoneID |
||||
node NodeID |
||||
addr netip.Addr |
||||
} |
||||
|
||||
func RZNTest(t *testing.T, |
||||
fnName string, fn func(RegionID, ZoneID, NodeID) (netip.Addr, error), |
||||
cases []RZNTestCase) { |
||||
//
|
||||
for i, tc := range cases { |
||||
s := fmt.Sprintf("%s(%v, %v, %v)", fnName, |
||||
tc.region, |
||||
tc.zone, |
||||
tc.node, |
||||
) |
||||
|
||||
addr, err := fn(tc.region, tc.zone, tc.node) |
||||
|
||||
switch { |
||||
case !tc.addr.IsValid(): |
||||
// expect error
|
||||
if err != nil { |
||||
t.Logf("[%v/%v]: %s → %s", i, len(cases), s, err) |
||||
} else { |
||||
t.Errorf("ERROR: [%v/%v]: %s → %s (expected %s)", i, len(cases), s, addr, "error") |
||||
} |
||||
case err != nil: |
||||
t.Errorf("ERROR: [%v/%v]: %s → %s (expected %s)", i, len(cases), s, err, tc.addr) |
||||
case addr.Compare(tc.addr) != 0: |
||||
t.Errorf("ERROR: [%v/%v]: %s → %s (expected %s)", i, len(cases), s, addr, tc.addr) |
||||
default: |
||||
t.Logf("[%v/%v]: %s → %s", i, len(cases), s, addr) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,116 @@
|
||||
// Package rings provides logic to work with the four rings
|
||||
// of a cluster
|
||||
package rings |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strconv" |
||||
"syscall" |
||||
|
||||
"darvaza.org/core" |
||||
) |
||||
|
||||
const ( |
||||
// UnspecifiedRingID is the zero value of RingID and not considered
|
||||
// valid.
|
||||
UnspecifiedRingID RingID = iota |
||||
RingZeroID // RingZeroID is the RingID for RingZero (backbone)
|
||||
RingOneID // RingOneID is the RingID for RingOne (local zone)
|
||||
RingTwoID // RingTwoID is the RingID for RingTwo (region services)
|
||||
RingThreeID // RingThreeID is the RingID for RingThree (region cluster pods)
|
||||
|
||||
// RingMax indicates the highest [Ring] identifier
|
||||
RingMax = RingThreeID |
||||
|
||||
// RegionMax indicates the highest number that can be used for a [RegionID].
|
||||
RegionMax = (1 << 4) - 1 |
||||
// ZoneMax indicates the highest number that can be used for a [ZoneID].
|
||||
ZoneMax = (1 << 4) - 1 |
||||
// NodeMax indicates the highest number that can be used for a [NodeID].
|
||||
NodeMax = (1 << 12) - 2 |
||||
// NodeZeroMax indicates the highest number that can be used for a [NodeID]
|
||||
// when its a gateway connected to Ring 0 (backbone).
|
||||
NodeZeroMax = (1 << 8) - 2 |
||||
|
||||
// RingZeroBits indicates the size of the prefix on the ring 0 (backbone) network.
|
||||
RingZeroBits = 16 |
||||
// RingOneBits indicates the size of the prefix on the ring 1 (lan) network.
|
||||
RingOneBits = 20 |
||||
// RingTwoBits indicates the size of the prefix on the ring 2 (services) network
|
||||
// of all kubernetes clusters.
|
||||
RingTwoBits = 20 |
||||
// RingThreeBits indicates the size of the prefix on the ring 3 (pods) network
|
||||
// of the kubernetes cluster of a region.
|
||||
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 |
||||
|
||||
// Valid tells a [NodeID] is within the valid range.
|
||||
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)) |
||||
} |
||||
} |
@ -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,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() |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue