Compare commits

...

6 Commits

Author SHA1 Message Date
amery bc91796464 rings: DecodeRingZeroAddress() and DecodeRingOneAddress()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-28 02:36:25 +00:00
amery ac091dcca9 rings: DecodeAddress() [WIP]
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-28 02:35:31 +00:00
amery 16f6f1141c rings: RingTwoPrefix()
Ring 2 is the service network shared by all kubernetes clusters.

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-27 22:14:11 +00:00
amery 1d7b37c3e7 rings: RingThreePrefix()
Ring 3 corresponds to the pods of the kubernetes cluster of a region

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-27 22:14:11 +00:00
amery 127a5830cc rings: RingZeroPrefix()/RingZeroAddress()
Ring zero corresponds to the backbone that connects all zones.

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-27 22:14:11 +00:00
amery 859e307ac4 rings: RingOnePrefix()/RingOneAddress()
Ring one designates the (virtual) local network of a zone
within a region.

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-27 22:14:11 +00:00
3 changed files with 368 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
package rings
import (
"net/netip"
"darvaza.org/core"
)
// DecodeAddress ... TODO
//
// revive:disable:function-result-limit
func DecodeAddress(addr netip.Addr) (Ring, RegionID, ZoneID, uint) {
// revive:enable:function-result-limit
if addr.IsValid() {
if addr.Is4In6() {
addr = addr.Unmap()
}
if addr.Is4() {
a4 := addr.As4()
return unsafeDecodeAddress(a4[0], a4[1], a4[2], a4[3])
}
}
return InvalidRing, 0, 0, 0
}
// revive:disable:function-result-limit
func unsafeDecodeAddress(a, b, c, d byte) (Ring, RegionID, ZoneID, uint) {
// revive:enable:function-result-limit
switch {
case a != 10:
return InvalidRing, 0, 0, 0
case b&0xf0 != 0:
k := RingThree
r := RegionID(b >> 4)
n2 := uint(b & 0x0f)
n1 := uint(c)
n0 := uint(d)
n := n0 + n1<<8 + n2<<16
return k, r, 0, n
case b&0x0f != 0:
r := RegionID(b)
z := ZoneID(c >> 4)
k := core.IIf(z == 0, RingTwo, RingOne)
n1 := uint(c & 0x0f)
n0 := uint(d)
n := n0 + n1<<8
return k, r, z, n
default: // b == 0
k := RingZero
r := RegionID(c >> 4)
z := ZoneID(c & 0xf)
n := uint(d)
return k, r, z, n
}
}
+197
View File
@@ -3,12 +3,27 @@
package rings package rings
import ( import (
"net/netip"
"syscall" "syscall"
"darvaza.org/core" "darvaza.org/core"
) )
// Ring ... TODO
type Ring int
const ( const (
// InvalidRing ... TODO
InvalidRing Ring = iota - 1
// RingZero ... TODO
RingZero
// RingOne ... TODO
RingOne
// RingTwo ... TODO
RingTwo
// RingThree ... TODO
RingThree
// RegionMax indicates the highest number that can be used for a [RegionID]. // RegionMax indicates the highest number that can be used for a [RegionID].
RegionMax = (1 << 4) - 1 RegionMax = (1 << 4) - 1
// ZoneMax indicates the highest number that can be used for a [ZoneID]. // ZoneMax indicates the highest number that can be used for a [ZoneID].
@@ -18,6 +33,17 @@ const (
// NodeZeroMax indicates the highest number that can be used for a [NodeID] // NodeZeroMax indicates the highest number that can be used for a [NodeID]
// when its a gateway connected to Ring 0 (backbone). // when its a gateway connected to Ring 0 (backbone).
NodeZeroMax = (1 << 8) - 1 NodeZeroMax = (1 << 8) - 1
// 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
) )
// RegionID is the identifier of a region, valid between 1 and [RegionMax]. // RegionID is the identifier of a region, valid between 1 and [RegionMax].
@@ -47,3 +73,174 @@ func (n NodeID) ValidZero() bool { return n > 0 && n <= NodeZeroMax }
func ErrOutOfRange[T ~int | ~uint32](value T, field string) error { func ErrOutOfRange[T ~int | ~uint32](value T, field string) error {
return core.Wrap(syscall.EINVAL, "%s out of range (%v)", field, value) return core.Wrap(syscall.EINVAL, "%s out of range (%v)", field, value)
} }
// 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
}
// 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(addr)
if k == RingZero {
return r, z, NodeID(n), true
}
return 0, 0, 0, false
}
// 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
}
// 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(addr)
if k == RingOne {
return r, z, NodeID(n), r != 0 && z != 0
}
return 0, 0, 0, false
}
// 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
}
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 {
r := uint(region)
z := uint(zone)
n := uint(node)
n1 := n >> 8
n0 := n >> 0
return AddrFrom4(10, r, z<<4+n1, n0)
}
func unsafeRingTwoAddress(region RegionID, n uint) netip.Addr {
r := uint(region)
n1 := n >> 8
n0 := n >> 0
return AddrFrom4(10, r, n1, n0)
}
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)
}
+109
View File
@@ -0,0 +1,109 @@
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)
}
}
}
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)
}
}
}