Compare commits

..

1 Commits

Author SHA1 Message Date
amery 461b6948d6 rings: fix NodeZeroMax/NodeMax to not accept .255
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-06-03 13:34:44 +00:00
13 changed files with 91 additions and 203 deletions
+1 -2
View File
@@ -15,8 +15,7 @@ TMPDIR ?= .tmp
REVIVE ?= $(GOBIN)/revive REVIVE ?= $(GOBIN)/revive
REVIVE_CONF ?= $(TOOLSDIR)/revive.toml REVIVE_CONF ?= $(TOOLSDIR)/revive.toml
REVIVE_RUN_ARGS ?= -config $(REVIVE_CONF) -formatter friendly REVIVE_RUN_ARGS ?= -config $(REVIVE_CONF) -formatter friendly
REVIVE_VERSION ?= v1.3.7 REVIVE_INSTALL_URL ?= github.com/mgechev/revive@master
REVIVE_INSTALL_URL ?= github.com/mgechev/revive@$(REVIVE_VERSION)
GO_INSTALL_URLS = \ GO_INSTALL_URLS = \
$(REVIVE_INSTALL_URL) \ $(REVIVE_INSTALL_URL) \
+1 -1
View File
@@ -52,7 +52,7 @@ func populateDNSManager(mgr *dns.Manager, m *cluster.Cluster) error {
m.ForEachZone(func(z *cluster.Zone) bool { m.ForEachZone(func(z *cluster.Zone) bool {
z.ForEachMachine(func(p *cluster.Machine) bool { z.ForEachMachine(func(p *cluster.Machine) bool {
err = mgr.AddHost(ctx, z.Name, int(p.ID), p.IsActive(), p.PublicAddresses...) err = mgr.AddHost(ctx, z.Name, p.ID, p.IsActive(), p.PublicAddresses...)
return err != nil return err != nil
}) })
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"os" "os"
"strconv"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -127,7 +128,7 @@ func gatewayListAll(zi cluster.ZoneIterator) error {
return false return false
} }
for _, i := range ids { for _, i := range ids {
sIDs = append(sIDs, i.String()) sIDs = append(sIDs, strconv.Itoa(i))
} }
b.WriteString(strings.Join(sIDs, ", ")) b.WriteString(strings.Join(sIDs, ", "))
b.WriteString("\n") b.WriteString("\n")
+3 -5
View File
@@ -6,18 +6,16 @@ import (
"os" "os"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
func (m *Cluster) init(opts *ScanOptions) error { func (m *Cluster) init(opts *ScanOptions) error {
for _, fn := range []func(*ScanOptions) error{ for _, fn := range []func(*ScanOptions) error{
m.initZones, m.initZones,
m.initRegions,
m.scanZoneIDs, m.scanZoneIDs,
m.scanSort, m.scanSort,
m.scanGateways, m.scanGateways,
m.initCephMonitors, m.initCephMonitors,
m.initRegions,
} { } {
if err := fn(opts); err != nil { if err := fn(opts); err != nil {
return err return err
@@ -47,7 +45,7 @@ func (m *Cluster) initZones(opts *ScanOptions) error {
func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error { func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error {
var hasMissing bool var hasMissing bool
var lastMachineID rings.NodeID var lastMachineID int
z.zones = m z.zones = m
z.logger = m z.logger = m
@@ -60,7 +58,7 @@ func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error {
case p.ID == 0: case p.ID == 0:
hasMissing = true hasMissing = true
case p.ID > lastMachineID: case p.ID > lastMachineID:
lastMachineID = p.ID lastMachineID = z.ID
} }
return false return false
+2 -4
View File
@@ -7,8 +7,6 @@ import (
"strings" "strings"
"darvaza.org/core" "darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
const ( const (
@@ -25,9 +23,9 @@ func (m *Cluster) scan(opts *ScanOptions) error {
for _, fn := range []func(*ScanOptions) error{ for _, fn := range []func(*ScanOptions) error{
m.scanDirectory, m.scanDirectory,
m.scanMachines, m.scanMachines,
m.initRegions,
m.scanZoneIDs, m.scanZoneIDs,
m.scanSort, m.scanSort,
m.initRegions,
m.scanGateways, m.scanGateways,
m.scanCephMonitors, m.scanCephMonitors,
} { } {
@@ -116,7 +114,7 @@ func (m *Cluster) scanMachines(opts *ScanOptions) error {
func (m *Cluster) scanZoneIDs(_ *ScanOptions) error { func (m *Cluster) scanZoneIDs(_ *ScanOptions) error {
var hasMissing bool var hasMissing bool
var lastZoneID rings.ZoneID var lastZoneID int
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
switch { switch {
+45 -30
View File
@@ -6,8 +6,6 @@ import (
"io" "io"
"sort" "sort"
"strings" "strings"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
// Env is a shell environment factory for this cluster // Env is a shell environment factory for this cluster
@@ -37,8 +35,8 @@ func (m *Cluster) Env(export bool) (*Env, error) {
} }
// Zones returns the list of Zone IDs // Zones returns the list of Zone IDs
func (m *Env) Zones() []rings.ZoneID { func (m *Env) Zones() []int {
var zones []rings.ZoneID var zones []int
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
zones = append(zones, z.ID) zones = append(zones, z.ID)
@@ -53,7 +51,7 @@ func (m *Env) Regions() []string {
var regions []string var regions []string
m.ForEachRegion(func(r *Region) bool { m.ForEachRegion(func(r *Region) bool {
if r.IsPrimary() { if r.Cluster != nil {
regions = append(regions, r.Name) regions = append(regions, r.Name)
} }
@@ -72,8 +70,8 @@ func (m *Env) WriteTo(w io.Writer) (int64, error) {
m.writeEnvVar(&buf, m.cephFSID, "FSID") m.writeEnvVar(&buf, m.cephFSID, "FSID")
} }
m.writeEnvVar(&buf, genEnvStrings(m.Regions()), "REGIONS") m.writeEnvVarStrings(&buf, m.Regions(), "REGIONS")
m.writeEnvVar(&buf, genEnvInts(m.Zones()), "ZONES") m.writeEnvVarInts(&buf, m.Zones(), "ZONES")
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
m.writeEnvZone(&buf, z) m.writeEnvZone(&buf, z)
@@ -94,7 +92,7 @@ func (m *Env) writeEnvZone(w io.Writer, z *Zone) {
// ZONE{zoneID}_GW // ZONE{zoneID}_GW
gateways, _ := z.GatewayIDs() gateways, _ := z.GatewayIDs()
m.writeEnvVar(w, genEnvInts(gateways), "ZONE%v_%s", zoneID, "GW") m.writeEnvVarInts(w, gateways, "ZONE%v_%s", zoneID, "GW")
// ZONE{zoneID}_REGION // ZONE{zoneID}_REGION
m.writeEnvVar(w, genEnvZoneRegion(z), "ZONE%v_%s", zoneID, "REGION") m.writeEnvVar(w, genEnvZoneRegion(z), "ZONE%v_%s", zoneID, "REGION")
@@ -109,6 +107,32 @@ func (m *Env) writeEnvZone(w io.Writer, z *Zone) {
m.writeEnvVar(w, genEnvZoneCephMonIDs(monitors), "MON%v_%s", 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...)
}
func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) { func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) {
var prefix string var prefix string
@@ -131,23 +155,6 @@ func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) {
} }
} }
func genEnvInts[T ~int | ~uint](values []T) string {
var buf bytes.Buffer
for _, v := range values {
if buf.Len() > 0 {
_, _ = buf.WriteRune(' ')
}
_, _ = buf.WriteString(fmt.Sprintf("%v", v))
}
return buf.String()
}
func genEnvStrings(values []string) string {
return strings.Join(values, " ")
}
func genEnvZoneNodes(z *Zone) string { func genEnvZoneNodes(z *Zone) string {
if n := z.Len(); n > 0 { if n := z.Len(); n > 0 {
s := make([]string, 0, n) s := make([]string, 0, n)
@@ -157,16 +164,24 @@ func genEnvZoneNodes(z *Zone) string {
return false return false
}) })
return genEnvStrings(s) return strings.Join(s, " ")
} }
return "" return ""
} }
func genEnvZoneRegion(z *Zone) string { func genEnvZoneRegion(z *Zone) string {
if z != nil && z.region != nil { var region string
return z.region.Name
} z.ForEachRegion(func(r *Region) bool {
return "" if r.Cluster != nil {
region = r.Name
return true
}
return false
})
return region
} }
func genEnvZoneCephMonNames(m Machines) string { func genEnvZoneCephMonNames(m Machines) string {
+2 -4
View File
@@ -3,8 +3,6 @@ package cluster
import ( import (
"net/netip" "net/netip"
"strings" "strings"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
// revive:disable:line-length-limit // revive:disable:line-length-limit
@@ -14,7 +12,7 @@ type Machine struct {
zone *Zone zone *Zone
logger `json:"-" yaml:"-"` logger `json:"-" yaml:"-"`
ID rings.NodeID ID int
Name string `json:"-" yaml:"-"` Name string `json:"-" yaml:"-"`
Inactive bool `json:"inactive,omitempty" yaml:"inactive,omitempty"` Inactive bool `json:"inactive,omitempty" yaml:"inactive,omitempty"`
@@ -76,7 +74,7 @@ func (m *Machine) SetGateway(enabled bool) error {
} }
// Zone indicates the [Zone] this machine belongs to // Zone indicates the [Zone] this machine belongs to
func (m *Machine) Zone() rings.ZoneID { func (m *Machine) Zone() int {
return m.zone.ID return m.zone.ID
} }
+1 -2
View File
@@ -8,7 +8,6 @@ import (
"darvaza.org/core" "darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
"git.jpi.io/amery/jpictl/pkg/wireguard" "git.jpi.io/amery/jpictl/pkg/wireguard"
) )
@@ -224,7 +223,7 @@ func (m *Machine) applyWireguardPeerConfig(ring int, pc wireguard.PeerConfig) er
} }
} }
func (m *Machine) applyZoneNodeID(zoneID rings.ZoneID, nodeID rings.NodeID) error { func (m *Machine) applyZoneNodeID(zoneID, nodeID int) error {
switch { switch {
case zoneID == 0: case zoneID == 0:
return fmt.Errorf("invalid %s", "zoneID") return fmt.Errorf("invalid %s", "zoneID")
+1 -2
View File
@@ -9,7 +9,6 @@ import (
"time" "time"
"darvaza.org/core" "darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
// LookupNetIP uses the DNS Resolver to get the public addresses associated // LookupNetIP uses the DNS Resolver to get the public addresses associated
@@ -66,7 +65,7 @@ func (m *Machine) setID() error {
return err return err
} }
m.ID = rings.NodeID(id) m.ID = int(id)
return nil return nil
} }
+2 -99
View File
@@ -3,8 +3,6 @@ package cluster
import ( import (
"bytes" "bytes"
"path/filepath" "path/filepath"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
var ( var (
@@ -26,15 +24,8 @@ type Region struct {
zones []*Zone zones []*Zone
Name string Name string
ID rings.RegionID `json:",omitempty" yaml:",omitempty"` Cluster *string `json:",omitempty" yaml:",omitempty"`
Cluster *string `json:",omitempty" yaml:",omitempty"` Regions []string `json:",omitempty" yaml:",omitempty"`
Regions []string `json:",omitempty" yaml:",omitempty"`
}
// IsPrimary indicates the region is primary and corresponds
// to a kubernetes cluster.
func (r *Region) IsPrimary() bool {
return r != nil && r.Cluster != nil
} }
// ForEachRegion calls a function for each Region of the cluster // ForEachRegion calls a function for each Region of the cluster
@@ -101,8 +92,6 @@ func (m *Cluster) initRegions(_ *ScanOptions) error {
} }
m.sortRegions() m.sortRegions()
m.scanRegionID()
m.computeZonesRegion()
return nil return nil
} }
@@ -221,92 +210,6 @@ func (m *Cluster) finishRegion(r *Region) {
r.Regions = sub r.Regions = sub
} }
// revive:disable:cognitive-complexity
func (m *Cluster) scanRegionID() {
// revive:enable:cognitive-complexity
var max rings.RegionID
var missing bool
// check IDs
ids := make(map[rings.RegionID]bool)
fn := func(r *Region) bool {
var term bool
switch {
case !r.IsPrimary():
// secondary, no ID.
r.ID = 0
case !r.ID.Valid():
// primary without ID
missing = true
case ids[r.ID]:
// duplicate
m.error(nil).WithField("region", r.Name).Print("duplicate ID")
missing = true
r.ID = 0
default:
ids[r.ID] = true
if r.ID > max {
max = r.ID
}
}
return term
}
m.ForEachRegion(fn)
if missing {
// assign missing IDs
fn := func(r *Region) bool {
var term bool
switch {
case !r.IsPrimary():
// ignore secondary
case r.ID.Valid():
// already has an ID
default:
r.ID = max + 1
max = r.ID
}
return term
}
m.ForEachRegion(fn)
}
}
func (m *Cluster) computeZonesRegion() {
fn := func(r *Region, z *Zone) {
if z.region != nil {
m.error(nil).
WithField("zone", z.Name).
WithField("region", []string{
z.region.Name,
r.Name,
}).
Print("zone in two regions")
} else {
z.region = r
}
}
m.ForEachRegion(func(r *Region) bool {
var term bool
if r.IsPrimary() {
r.ForEachZone(func(z *Zone) bool {
fn(r, z)
return term
})
}
return term
})
}
func (m *Cluster) getRegion(name string) (*Region, bool) { func (m *Cluster) getRegion(name string) (*Region, bool) {
for i := range m.Regions { for i := range m.Regions {
r := &m.Regions[i] r := &m.Regions[i]
+27 -16
View File
@@ -5,11 +5,14 @@ import (
"io/fs" "io/fs"
"net/netip" "net/netip"
"git.jpi.io/amery/jpictl/pkg/rings"
"git.jpi.io/amery/jpictl/pkg/wireguard" "git.jpi.io/amery/jpictl/pkg/wireguard"
) )
const ( const (
// MaxZoneID indicates the highest ID allowed for a Zone
MaxZoneID = 0xf
// MaxNodeID indicates the highest Machine ID allowed within a Zone
MaxNodeID = 0xff - 1
// RingsCount indicates how many wireguard rings we have // RingsCount indicates how many wireguard rings we have
RingsCount = 2 RingsCount = 2
// RingZeroPort is the port wireguard uses for ring0 // RingZeroPort is the port wireguard uses for ring0
@@ -78,8 +81,8 @@ func canMergeKeyPairs(p1, p2 wireguard.KeyPair) bool {
type RingAddressEncoder struct { type RingAddressEncoder struct {
ID int ID int
Port uint16 Port uint16
Encode func(zoneID rings.ZoneID, nodeID rings.NodeID) (netip.Addr, bool) Encode func(zoneID, nodeID int) (netip.Addr, bool)
Decode func(addr netip.Addr) (zoneID rings.ZoneID, nodeID rings.NodeID, ok bool) Decode func(addr netip.Addr) (zoneID, nodeID int, ok bool)
} }
var ( var (
@@ -107,34 +110,42 @@ var (
// ValidZoneID checks if the given zoneID is a valid 4 bit zone number. // ValidZoneID checks if the given zoneID is a valid 4 bit zone number.
// //
// 0 is reserved, and only allowed when composing CIDRs. // 0 is reserved, and only allowed when composing CIDRs.
func ValidZoneID(zoneID rings.ZoneID) bool { func ValidZoneID(zoneID int) bool {
return zoneID == 0 || zoneID.Valid() switch {
case zoneID < 0 || zoneID > MaxZoneID:
return false
default:
return true
}
} }
// ValidNodeID checks if the given nodeID is a valid 8 bit number. // ValidNodeID checks if the given nodeID is a valid 8 bit number.
// nodeID is unique within a Zone. // nodeID is unique within a Zone.
// 0 is reserved, and only allowed when composing CIDRs. // 0 is reserved, and only allowed when composing CIDRs.
func ValidNodeID(nodeID rings.NodeID) bool { func ValidNodeID(nodeID int) bool {
return nodeID == 0 || nodeID.Valid() switch {
case nodeID < 0 || nodeID > MaxNodeID:
return false
default:
return true
}
} }
// ParseRingZeroAddress extracts zone and node ID from a wg0 [netip.Addr] // ParseRingZeroAddress extracts zone and node ID from a wg0 [netip.Addr]
// wg0 addresses are of the form `10.0.{{zoneID}}.{{nodeID}}` // wg0 addresses are of the form `10.0.{{zoneID}}.{{nodeID}}`
func ParseRingZeroAddress(addr netip.Addr) (zoneID rings.ZoneID, nodeID rings.NodeID, ok bool) { func ParseRingZeroAddress(addr netip.Addr) (zoneID int, nodeID int, ok bool) {
if addr.IsValid() { if addr.IsValid() {
a4 := addr.As4() a4 := addr.As4()
if a4[0] == 10 && a4[1] == 0 { if a4[0] == 10 && a4[1] == 0 {
zoneID = rings.ZoneID(a4[2]) return int(a4[2]), int(a4[3]), true
nodeID = rings.NodeID(a4[3])
return zoneID, nodeID, true
} }
} }
return 0, 0, false return 0, 0, false
} }
// RingZeroAddress returns a wg0 IP address // RingZeroAddress returns a wg0 IP address
func RingZeroAddress(zoneID rings.ZoneID, nodeID rings.NodeID) (netip.Addr, bool) { func RingZeroAddress(zoneID, nodeID int) (netip.Addr, bool) {
switch { switch {
case !ValidZoneID(zoneID) || !ValidNodeID(nodeID): case !ValidZoneID(zoneID) || !ValidNodeID(nodeID):
return netip.Addr{}, false return netip.Addr{}, false
@@ -146,13 +157,13 @@ func RingZeroAddress(zoneID rings.ZoneID, nodeID rings.NodeID) (netip.Addr, bool
// ParseRingOneAddress extracts zone and node ID from a wg1 [netip.Addr] // ParseRingOneAddress extracts zone and node ID from a wg1 [netip.Addr]
// wg1 addresses are of the form `10.{{zoneID << 4}}.{{nodeID}}` // wg1 addresses are of the form `10.{{zoneID << 4}}.{{nodeID}}`
func ParseRingOneAddress(addr netip.Addr) (zoneID rings.ZoneID, nodeID rings.NodeID, ok bool) { func ParseRingOneAddress(addr netip.Addr) (zoneID int, nodeID int, ok bool) {
if addr.IsValid() { if addr.IsValid() {
a4 := addr.As4() a4 := addr.As4()
if a4[0] == 10 && a4[2] == 0 { if a4[0] == 10 && a4[2] == 0 {
zoneID = rings.ZoneID(a4[1] >> 4) zoneID = int(a4[1] >> 4)
nodeID = rings.NodeID(a4[3]) nodeID = int(a4[3])
return zoneID, nodeID, true return zoneID, nodeID, true
} }
} }
@@ -160,7 +171,7 @@ func ParseRingOneAddress(addr netip.Addr) (zoneID rings.ZoneID, nodeID rings.Nod
} }
// RingOneAddress returns a wg1 IP address // RingOneAddress returns a wg1 IP address
func RingOneAddress(zoneID rings.ZoneID, nodeID rings.NodeID) (netip.Addr, bool) { func RingOneAddress(zoneID, nodeID int) (netip.Addr, bool) {
switch { switch {
case !ValidZoneID(zoneID) || !ValidNodeID(nodeID): case !ValidZoneID(zoneID) || !ValidNodeID(nodeID):
return netip.Addr{}, false return netip.Addr{}, false
+4 -7
View File
@@ -2,8 +2,6 @@ package cluster
import ( import (
"io/fs" "io/fs"
"git.jpi.io/amery/jpictl/pkg/rings"
) )
var ( var (
@@ -19,10 +17,9 @@ type ZoneIterator interface {
// affinity. // affinity.
type Zone struct { type Zone struct {
zones *Cluster zones *Cluster
region *Region
logger `json:"-" yaml:"-"` logger `json:"-" yaml:"-"`
ID rings.ZoneID ID int
Name string Name string
Regions []string `json:",omitempty" yaml:",omitempty"` Regions []string `json:",omitempty" yaml:",omitempty"`
@@ -34,7 +31,7 @@ func (z *Zone) String() string {
} }
// SetGateway configures a machine to be the zone's ring0 gateway // SetGateway configures a machine to be the zone's ring0 gateway
func (z *Zone) SetGateway(gatewayID rings.NodeID, enabled bool) error { func (z *Zone) SetGateway(gatewayID int, enabled bool) error {
var err error var err error
var found bool var found bool
@@ -59,8 +56,8 @@ func (z *Zone) SetGateway(gatewayID rings.NodeID, enabled bool) error {
} }
// GatewayIDs returns the list of IDs of machines that act as ring0 gateways // GatewayIDs returns the list of IDs of machines that act as ring0 gateways
func (z *Zone) GatewayIDs() ([]rings.NodeID, int) { func (z *Zone) GatewayIDs() ([]int, int) {
var out []rings.NodeID var out []int
z.ForEachMachine(func(p *Machine) bool { z.ForEachMachine(func(p *Machine) bool {
if p.IsGateway() { if p.IsGateway() {
out = append(out, p.ID) out = append(out, p.ID)
-30
View File
@@ -3,8 +3,6 @@
package rings package rings
import ( import (
"fmt"
"strconv"
"syscall" "syscall"
"darvaza.org/core" "darvaza.org/core"
@@ -39,20 +37,12 @@ type RegionID int
// Valid tells a [RegionID] is within the valid range. // Valid tells a [RegionID] is within the valid range.
func (n RegionID) Valid() bool { return n > 0 && n <= RegionMax } func (n RegionID) Valid() bool { return n > 0 && n <= RegionMax }
func (n RegionID) String() string {
return idString(n)
}
// ZoneID is the identifier of a zone within a region, valid between 1 and [ZoneMax]. // ZoneID is the identifier of a zone within a region, valid between 1 and [ZoneMax].
type ZoneID int type ZoneID int
// Valid tells a [ZoneID] is within the valid range. // Valid tells a [ZoneID] is within the valid range.
func (n ZoneID) Valid() bool { return n > 0 && n <= ZoneMax } func (n ZoneID) Valid() bool { return n > 0 && n <= ZoneMax }
func (n ZoneID) String() string {
return idString(n)
}
// NodeID is the identifier of a machine within a zone of a region, valid between // NodeID is the identifier of a machine within a zone of a region, valid between
// 1 and [NodeMax], but between 1 and [NodeZeroMax] if it will be a zone gateway. // 1 and [NodeMax], but between 1 and [NodeZeroMax] if it will be a zone gateway.
type NodeID int type NodeID int
@@ -63,28 +53,8 @@ func (n NodeID) Valid() bool { return n > 0 && n <= NodeMax }
// ValidZero tells a [NodeID] is within the valid range for a gateway. // ValidZero tells a [NodeID] is within the valid range for a gateway.
func (n NodeID) ValidZero() bool { return n > 0 && n <= NodeZeroMax } func (n NodeID) ValidZero() bool { return n > 0 && n <= NodeZeroMax }
func (n NodeID) String() string {
return idString(n)
}
// ErrOutOfRange is an error indicating the value of a field // ErrOutOfRange is an error indicating the value of a field
// is out of range. // is out of range.
func ErrOutOfRange[T ~int | ~uint32](value T, field string) error { func ErrOutOfRange[T ~int | ~uint32](value T, field string) error {
return core.Wrap(syscall.EINVAL, "%s out of range (%v)", field, value) return core.Wrap(syscall.EINVAL, "%s out of range (%v)", field, value)
} }
type intID interface {
~int
Valid() bool
}
func idString[T intID](p T) string {
switch {
case p == 0:
return "unspecified"
case p.Valid():
return strconv.Itoa(int(p))
default:
return fmt.Sprintf("invalid (%v)", int(p))
}
}