Compare commits

..

55 Commits

Author SHA1 Message Date
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
karasz e1186975a6 Merge pull request 'rings: Prefix and Address factories' (#48) from pr-amery-rings into main
Reviewed-on: #48
2024-05-29 17:44:11 +02:00
amery 72a2468a10 rings: RingTwoPrefix()
Ring 2 is the service network shared by all kubernetes clusters.

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-28 14:36:41 +00:00
amery 6142d0f7f0 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-28 14:09:06 +00:00
amery 0f177acf57 rings: RingZeroPrefix()/RingZeroAddress()
Ring zero corresponds to the backbone that connects all zones.

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-28 14:05:33 +00:00
amery 394a84c3ab 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-28 14:02:02 +00:00
amery 52e1195139 rings: introduce generic ErrOutOfRange() factory
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-27 22:14:11 +00:00
amery 378bab2f96 rings: introduce RegionID, ZoneID and NodeID
and a Valid() method to check if their value is within the
valid range.

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-27 22:14:11 +00:00
amery f45a8f21f3 Merge pull request 'rings: PrefixToRange(), AddrToU32(), AddrFromU32()' (#46)
Reviewed-on: #46
2024-05-28 00:13:37 +02:00
amery 686e6f2f73 Merge in pull request #47 (chores)
build-sys: update dependencies and fix revive's installation
2024-05-25 23:39:20 +02:00
amery 199ef88a65 build-sys: update dependencies
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-25 21:36:50 +00:00
amery addf138d36 build-sys: fix revive installation
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-25 21:36:06 +00:00
amery 3e90c7a30b rings: introduce PrefixToRange()
returning the beginning and end of a subnet

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-25 21:17:16 +00:00
amery 50436a320c rings: introduce AddrToU32() and AddrFromU32() helpers
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-25 21:17:16 +00:00
amery ac5827898b rings: introduce subpackage to deal with Ring addresses
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-05-25 21:15:36 +00:00
amery f854d87b16 Merge pull request 'env: export REGIONS and ZONE{zoneID}_REGION' (#45) from pr-amery-env-regions into main
Reviewed-on: #45
2024-03-19 21:11:49 +01:00
amery fefbfa16a2 env: export ZONE{zoneID}_REGION
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:47:42 +00:00
amery 7986e0fd3b env: add REGIONS list to output
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:47:42 +00:00
amery 3ba721bf7f env: add []string writer
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:47:42 +00:00
amery e410797413 env: use foo= instead of foo="" for empty variables
to improve readability

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:47:42 +00:00
karasz 3fdbb6f867 Merge pull request 'cluster: introduce RegionIterator, Zone.ForEachRegion and decouple getRegion from finishRegion' (#43) from pr-amery-regioniter into main
Reviewed-on: #43
2024-03-19 17:46:54 +01:00
amery 0158dc3a3c cluster: add recursive RegionIterator to Zone
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:46:13 +00:00
amery 9226cf2dfd cluster: introduce RegionIterator
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:46:13 +00:00
amery 173dba0c3b cluster: decouple getRegion() from finishRegion()
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:46:13 +00:00
karasz 3a2a20a611 Merge pull request 'cluster: introduce Region.Cluser and m/{region}/k8s_token to store a cluster's token' (#42) from pr-amery-k8s_token into main
Reviewed-on: #42
2024-03-19 17:44:47 +01:00
amery 019143820f cluster: remove k8s_token files from zones on write if found
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:42:56 +00:00
amery d3bbe6d270 cluster: introduce Region.Cluster and m/{region}/k8s_token
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:42:56 +00:00
amery 7125e28b74 cluster: extend usage of the ZoneRegionsFileName constant
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-19 16:42:56 +00:00
karasz 7f5ac151c8 Merge pull request 'wireguard: switch from gcfg to asciigoat.org/ini/basic' (#8) from pr-amery-wireguard-ini into main
Reviewed-on: #8
2024-03-09 11:10:02 +01:00
karasz 28bcaa2838 Merge pull request 'jpictl: add --version and version command' (#44) from pr-amery-version into main
Reviewed-on: #44
2024-03-09 11:09:33 +01:00
karasz 3cafb1a4e2 Merge pull request 'ceph: prevent osd label update on startup' (#41) from pr-amery-ceph into main
Reviewed-on: #41
2024-03-09 11:08:30 +01:00
amery 09bec11506 jpictl: add --version and version command
from `git describe` using go:generate and embed

Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-02 23:15:56 +00:00
amery 14686ff5a8 ceph: prevent osd label update on startup
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-02 19:16:37 +00:00
amery 3c24e24d71 wireguard: switch from gcfg to asciigoat.org/ini/basic
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-02 18:58:39 +00:00
amery 45dc2291bf Merge pull request 'remove unused arguments and update dependencies' (#40) from pr-amery-chores into main
Reviewed-on: #40
2024-03-02 19:55:57 +01:00
amery b019d303d4 chore: update dependencies
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-02 18:53:08 +00:00
amery f64f4e08fe cluster: remove unused variables on scan option factories
Signed-off-by: Alejandro Mery <amery@jpi.io>
2024-03-02 18:51:10 +00:00
amery cf09cfa743 Merge pull request 'cluster: handle regions on filesystem' (#39)
Reviewed-on: #39
2023-10-31 19:31:01 +01:00
amery 00cf3959a2 cluster: load regions when scanning a directory
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-10-30 19:47:20 +00:00
amery 0db3e18227 cluster: introduce SyncRegions() to write regions file
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-10-30 19:47:20 +00:00
amery 0094450ca8 cluster: fix regions/zones mapping when the region exists
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-10-30 19:47:20 +00:00
35 changed files with 1937 additions and 485 deletions
+1
View File
@@ -1 +1,2 @@
.tmp
.version
+2 -1
View File
@@ -15,7 +15,8 @@ TMPDIR ?= .tmp
REVIVE ?= $(GOBIN)/revive
REVIVE_CONF ?= $(TOOLSDIR)/revive.toml
REVIVE_RUN_ARGS ?= -config $(REVIVE_CONF) -formatter friendly
REVIVE_INSTALL_URL ?= github.com/mgechev/revive
REVIVE_VERSION ?= v1.3.7
REVIVE_INSTALL_URL ?= github.com/mgechev/revive@$(REVIVE_VERSION)
GO_INSTALL_URLS = \
$(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
})
+1 -2
View File
@@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"os"
"strconv"
"strings"
"github.com/spf13/cobra"
@@ -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")
+5 -2
View File
@@ -2,6 +2,8 @@
package main
import (
_ "embed"
"github.com/spf13/cobra"
)
@@ -12,8 +14,9 @@ const (
var (
rootCmd = &cobra.Command{
Use: CmdName,
Short: "control tool for jpi.cloud",
Use: CmdName,
Short: "control tool for jpi.cloud",
Version: version,
}
)
+31
View File
@@ -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)
}
+27 -27
View File
@@ -4,30 +4,30 @@ go 1.19
require (
asciigoat.org/ini v0.2.5
darvaza.org/core v0.10.0
darvaza.org/resolver v0.5.8
darvaza.org/sidecar v0.0.8
darvaza.org/slog v0.5.4
darvaza.org/slog/handlers/discard v0.4.6
github.com/gofrs/uuid/v5 v5.0.0
darvaza.org/core v0.13.3
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.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.4
github.com/spf13/cobra v1.7.0
golang.org/x/crypto v0.14.0
golang.org/x/net v0.17.0
gopkg.in/gcfg.v1 v1.2.3
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
gopkg.in/yaml.v3 v3.0.1
)
require (
asciigoat.org/core v0.3.9 // indirect
darvaza.org/slog/handlers/filter v0.4.6 // indirect
darvaza.org/slog/handlers/zerolog v0.4.6 // indirect
github.com/BurntSushi/toml v1.3.2 // 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.15.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
@@ -35,18 +35,18 @@ require (
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.56 // 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.4 // indirect
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 // indirect
github.com/rs/zerolog v1.31.0 // 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.13.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/warnings.v0 v0.1.2 // 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
)
+56 -60
View File
@@ -2,53 +2,51 @@ asciigoat.org/core v0.3.9 h1:hgDDz4ecm3ZvehX++m8A/IzAt+B5oDPiRtxatzfUHPQ=
asciigoat.org/core v0.3.9/go.mod h1:CAaHwyw8MpAq4a1MYtN2dxJrsK+hmIdW50OndaQZYPI=
asciigoat.org/ini v0.2.5 h1:4gRIp9rU+XQt8+HMqZO5R7GavMv9Yl2+N+je6djDIAE=
asciigoat.org/ini v0.2.5/go.mod h1:gmXzJ9XFqf1NLk5nQkj04USQ4tMtdRJHNQX6vp3DzjU=
darvaza.org/core v0.10.0 h1:/nQOSWnMgWW8ZJmv3AEdTgIK+Pg4lkPd+VNejL84q3M=
darvaza.org/core v0.10.0/go.mod h1:72iWMVoXjMHjsPSlctDzA7yKzwXsj5dO+se6F9B3ERs=
darvaza.org/resolver v0.5.8 h1:y410WQ3vRCgE7437eyA55cNMZRP32qYXiokLejkFQeg=
darvaza.org/resolver v0.5.8/go.mod h1:QnfX+eSZZZbmnE3n+6w4gfqXDH1Gj2MWJVQxhlQDHq8=
darvaza.org/sidecar v0.0.8 h1:vsWK2SZfBYzU999brmT8gzVeCRKbuNQZOVdG5zxjO6U=
darvaza.org/sidecar v0.0.8/go.mod h1:G96TMPge2jqpKMpaCWc9zwdfaJTmko7dMMWXwDsdocM=
darvaza.org/slog v0.5.4 h1:xzlWVzYh4tuZLnj4A9tOHXfn/SAEIkApXPvK3YDiW9g=
darvaza.org/slog v0.5.4/go.mod h1:QFtY3QoQ7xxww85umlEKPcMCNzqNrHYqnj53KehsmBU=
darvaza.org/slog/handlers/discard v0.4.6 h1:TatHJn34y6eKQzNRHSo6lGZnJg4SLOGaWstlvwwOyrE=
darvaza.org/slog/handlers/discard v0.4.6/go.mod h1:AG8WKr7m11NPPzvHW/b8nCT5RvYR9RZcIT/NWUOoMAo=
darvaza.org/slog/handlers/filter v0.4.6 h1:AI5AQDyXS534QeXIV54pAKxplA6AVZNr4H2PEmAXT0k=
darvaza.org/slog/handlers/filter v0.4.6/go.mod h1:MGTKdlnA/FanOn3GU2mltzwBn41HgSxxNeWUQEKFbl8=
darvaza.org/slog/handlers/zerolog v0.4.6 h1:Di+FXUD2R2pKUrynaidyXzS0WsrEiwbL11LQlQzwZv4=
darvaza.org/slog/handlers/zerolog v0.4.6/go.mod h1:r5B9/FQ256R3Wo5vFLOa2YarM2P8WOjVjFn8xHikNjk=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
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.3 h1:DOsidY49WXsWiJulOIxDq578h/3ekgx0trWxbvgv5bc=
darvaza.org/core v0.13.3/go.mod h1:47Ydh67KnzjLNu1mzX3r2zpphbxQqEaihMsUq5GflQ4=
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=
darvaza.org/sidecar v0.4.0/go.mod h1:fUzjcFM4rN3bSEl4BKvok3MLpZWEhEa9+0/egmtpfMY=
darvaza.org/slog v0.5.7 h1:JWC0OqvzR435AidIRDp4T9kdWTURWkUjzP4R78Koq1Q=
darvaza.org/slog v0.5.7/go.mod h1:12L03t+KYhsZ9IbfF+8if5w9Y91af2par+bSzeBVqIQ=
darvaza.org/slog/handlers/discard v0.4.11 h1:wr34OnDoRaMV1eGgW7yUaupQxjkTnuHrJmYRPj64RHM=
darvaza.org/slog/handlers/discard v0.4.11/go.mod h1:ynxyLmZzZ5mP4ACLhQs4MEuDyhkIzjz6DfBHUjhnIK4=
darvaza.org/slog/handlers/filter v0.4.9 h1:xD8OBwlJytpiwTSDDZqUuNSOsJuaManXQiOj9WEStr8=
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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
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.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
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=
github.com/hack-pad/hackpadfs v0.2.1 h1:FelFhIhv26gyjujoA/yeFO+6YGlqzmc9la/6iKMIxMw=
github.com/hack-pad/hackpadfs v0.2.1/go.mod h1:khQBuCEwGXWakkmq8ZiFUvUZz84ZkJ2KNwKvChs4OrU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/libdns/cloudflare v0.1.0 h1:93WkJaGaiXCe353LHEP36kAWCUw0YjFqwhkBkU2/iic=
github.com/libdns/cloudflare v0.1.0/go.mod h1:a44IP6J1YH6nvcNl1PverfJviADgXUnsozR3a7vBKN8=
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -60,10 +58,10 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
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.4 h1:k/tO3XTaWY4DEHal9tWBkkUMJYO/dLDVyMmAQxmIMDc=
github.com/mgechev/revive v1.3.4/go.mod h1:W+pZCMu9qj8Uhfs1iJMQsEFLRozUfvwFwqVvRbSNLVw=
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
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=
@@ -74,17 +72,19 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
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.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
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.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY=
github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
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/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
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=
@@ -94,30 +94,26 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
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/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/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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
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=
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+3
View File
@@ -39,6 +39,9 @@ func writeGlobalToBuffer(w *bytes.Buffer, c *GlobalConfig) {
_, _ = fmt.Fprintf(w, "%s = %s\n", "mon_initial_members", strings.Join(c.Monitors, ", "))
_, _ = fmt.Fprintf(w, "%s = %s\n", "mon_host", joinAddrs(c.MonitorsAddr, ", "))
_, _ = fmt.Fprintf(w, "%s = %s\n", "cluster_network", c.ClusterNetwork.String())
_, _ = fmt.Fprintf(w, "\n; %s\n", "don't rewrite labels on startup")
_, _ = fmt.Fprintf(w, "%s = %s\n", "osd_class_update_on_start", "false")
}
func joinAddrs(addrs []netip.Addr, sep string) string {
+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)
+2 -2
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
+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
+123 -27
View File
@@ -2,15 +2,30 @@ package cluster
import (
"io/fs"
"path"
"sort"
"strings"
"darvaza.org/core"
"git.jpi.io/amery/jpictl/pkg/rings"
)
const (
// ZoneRegionsFileName indicates the file containing
// region names as references
ZoneRegionsFileName = "regions"
// RegionClusterTokenFileName contains the kubernetes
// token of the cluster this region represents
RegionClusterTokenFileName = "k8s_token"
)
func (m *Cluster) scan(opts *ScanOptions) error {
for _, fn := range []func(*ScanOptions) error{
m.scanDirectory,
m.scanMachines,
m.initRegions,
m.scanZoneIDs,
m.scanSort,
m.scanGateways,
@@ -24,7 +39,7 @@ func (m *Cluster) scan(opts *ScanOptions) error {
return nil
}
func (m *Cluster) scanDirectory(_ *ScanOptions) error {
func (m *Cluster) scanDirectory(opts *ScanOptions) error {
// each directory is a zone
entries, err := fs.ReadDir(m.dir, ".")
if err != nil {
@@ -33,16 +48,14 @@ func (m *Cluster) scanDirectory(_ *ScanOptions) error {
for _, e := range entries {
if e.IsDir() {
z, err := m.newZone(e.Name())
ok, err := m.scanSubdirectory(opts, e.Name())
switch {
case err != nil:
return core.Wrap(err, e.Name())
case z.Machines.Len() == 0:
z.warn(nil).
WithField("zone", z.Name).
case !ok:
m.warn(nil).
WithField("zone", e.Name()).
Print("empty")
default:
m.Zones = append(m.Zones, z)
}
}
}
@@ -50,6 +63,27 @@ func (m *Cluster) scanDirectory(_ *ScanOptions) error {
return nil
}
func (m *Cluster) scanSubdirectory(_ *ScanOptions, name string) (bool, error) {
z, err := m.newZone(name)
switch {
case err != nil:
// somewhere went wrong scanning the subdirectory
return false, err
case z.Machines.Len() > 0:
// zones have machines and the regions they belong
m.Zones = append(m.Zones, z)
return true, nil
case len(z.Regions) > 0:
// regions have no machines but can include
// other regions
m.appendRegionRegions(name, z.Regions...)
return true, nil
default:
// empty
return false, nil
}
}
func (m *Cluster) newZone(name string) (*Zone, error) {
z := &Zone{
zones: m,
@@ -82,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 {
@@ -154,34 +188,96 @@ func (z *Zone) scan() error {
}
for _, e := range entries {
if e.IsDir() {
m := &Machine{
zone: z,
logger: z,
Name: e.Name(),
}
name := e.Name()
m.debug().
WithField("node", m.Name).
switch {
case name == ZoneRegionsFileName:
err = z.loadRegions()
case name == RegionClusterTokenFileName:
err = z.loadClusterToken()
case e.IsDir():
err = z.scanSubdirectory(name)
default:
z.warn(nil).
WithField("zone", z.Name).
Print("found")
WithField("filename", name).
Print("unknown")
}
if err := m.init(); err != nil {
m.error(err).
WithField("node", m.Name).
WithField("zone", z.Name).
Print()
return err
}
z.Machines = append(z.Machines, m)
if err != nil {
return err
}
}
return nil
}
func (z *Zone) loadRegions() error {
filename := path.Join(z.Name, ZoneRegionsFileName)
regions, err := z.zones.ReadLines(filename)
if err == nil {
// parsed
err = z.appendRegions(regions...)
if err != nil {
err = core.Wrap(err, filename)
}
}
return err
}
func (z *Zone) loadClusterToken() error {
var token string
filename := path.Join(z.Name, RegionClusterTokenFileName)
lines, err := z.zones.ReadLines(filename)
if err != nil {
return err
}
// first non-empty line
for _, s := range lines {
s = strings.TrimSpace(s)
if s != "" {
token = s
break
}
}
err = z.zones.setRegionClusterToken(z.Name, token)
if err != nil {
err = core.Wrap(err, filename)
}
return err
}
func (z *Zone) scanSubdirectory(name string) error {
m := &Machine{
zone: z,
logger: z,
Name: name,
}
m.debug().
WithField("node", m.Name).
WithField("zone", z.Name).
Print("found")
if err := m.init(); err != nil {
m.error(err).
WithField("node", m.Name).
WithField("zone", z.Name).
Print()
return err
}
z.Machines = append(z.Machines, m)
return nil
}
// GetGateway returns the first gateway found, if none
// files will be created to enable the first [Machine] to
// be one
+3 -3
View File
@@ -27,7 +27,7 @@ type ScanOptions struct {
// the DNS resolver to get PublicAddresses of nodes.
// Default is true
func ResolvePublicAddresses(resolve bool) ScanOption {
return func(m *Cluster, opt *ScanOptions) error {
return func(_ *Cluster, opt *ScanOptions) error {
opt.DontResolvePublicAddresses = !resolve
return nil
}
@@ -36,7 +36,7 @@ func ResolvePublicAddresses(resolve bool) ScanOption {
// WithLookuper specifies what resolver.Lookuper to use to
// find public addresses
func WithLookuper(h resolver.Lookuper) ScanOption {
return func(m *Cluster, opt *ScanOptions) error {
return func(m *Cluster, _ *ScanOptions) error {
if h == nil {
return fs.ErrInvalid
}
@@ -49,7 +49,7 @@ func WithLookuper(h resolver.Lookuper) ScanOption {
// public addresses. if nil is passed, the [net.Resolver] will be used.
// The default is using Cloudflare's 1.1.1.1.
func WithResolver(h resolver.Resolver) ScanOption {
return func(m *Cluster, opt *ScanOptions) error {
return func(m *Cluster, _ *ScanOptions) error {
if h == nil {
h = resolver.SystemResolver(true)
}
+63 -29
View File
@@ -4,12 +4,16 @@ import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"git.jpi.io/amery/jpictl/pkg/rings"
)
// Env is a shell environment factory for this cluster
type Env struct {
ZoneIterator
RegionIterator
cephFSID string
export bool
@@ -23,17 +27,18 @@ func (m *Cluster) Env(export bool) (*Env, error) {
}
env := &Env{
ZoneIterator: m,
cephFSID: fsid.String(),
export: export,
ZoneIterator: m,
RegionIterator: m,
cephFSID: fsid.String(),
export: export,
}
return env, nil
}
// Zones returns the list of Zone IDs
func (m *Env) Zones() []int {
var zones []int
func (m *Env) Zones() []rings.ZoneID {
var zones []rings.ZoneID
m.ForEachZone(func(z *Zone) bool {
zones = append(zones, z.ID)
@@ -43,6 +48,22 @@ func (m *Env) Zones() []int {
return zones
}
// Regions returns the list of primary regions
func (m *Env) Regions() []string {
var regions []string
m.ForEachRegion(func(r *Region) bool {
if r.IsPrimary() {
regions = append(regions, r.Name)
}
return false
})
sort.Strings(regions)
return regions
}
// WriteTo generates environment variables for shell scripts
func (m *Env) WriteTo(w io.Writer) (int64, error) {
var buf bytes.Buffer
@@ -51,7 +72,8 @@ func (m *Env) WriteTo(w io.Writer) (int64, error) {
m.writeEnvVar(&buf, m.cephFSID, "FSID")
}
m.writeEnvVarInts(&buf, m.Zones(), "ZONES")
m.writeEnvVar(&buf, genEnvStrings(m.Regions()), "REGIONS")
m.writeEnvVar(&buf, genEnvInts(m.Zones()), "ZONES")
m.ForEachZone(func(z *Zone) bool {
m.writeEnvZone(&buf, z)
@@ -72,7 +94,10 @@ func (m *Env) writeEnvZone(w io.Writer, z *Zone) {
// ZONE{zoneID}_GW
gateways, _ := z.GatewayIDs()
m.writeEnvVarInts(w, gateways, "ZONE%v_%s", zoneID, "GW")
m.writeEnvVar(w, genEnvInts(gateways), "ZONE%v_%s", zoneID, "GW")
// ZONE{zoneID}_REGION
m.writeEnvVar(w, genEnvZoneRegion(z), "ZONE%v_%s", zoneID, "REGION")
// Ceph
monitors := z.GetCephMonitors()
@@ -84,25 +109,6 @@ func (m *Env) writeEnvZone(w io.Writer, z *Zone) {
m.writeEnvVar(w, genEnvZoneCephMonIDs(monitors), "MON%v_%s", zoneID, "ID")
}
func (m *Env) writeEnvVarInts(w io.Writer, value []int, name string, args ...any) {
var s string
if n := len(value); n > 0 {
var buf bytes.Buffer
for i, v := range value {
if i != 0 {
_, _ = fmt.Fprint(&buf, " ")
}
_, _ = fmt.Fprintf(&buf, "%v", v)
}
s = buf.String()
}
m.writeEnvVar(w, s, name, args...)
}
func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) {
var prefix string
@@ -117,10 +123,31 @@ func (m *Env) writeEnvVar(w io.Writer, value string, name string, args ...any) {
if name != "" {
value = strings.TrimSpace(value)
_, _ = fmt.Fprintf(w, "%s%s=%q\n", prefix, name, value)
if value == "" {
_, _ = fmt.Fprintf(w, "%s%s=\n", prefix, name)
} else {
_, _ = fmt.Fprintf(w, "%s%s=%q\n", prefix, name, value)
}
}
}
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 {
if n := z.Len(); n > 0 {
s := make([]string, 0, n)
@@ -130,7 +157,14 @@ func genEnvZoneNodes(z *Zone) string {
return false
})
return strings.Join(s, " ")
return genEnvStrings(s)
}
return ""
}
func genEnvZoneRegion(z *Zone) string {
if z != nil && z.region != nil {
return z.region.Name
}
return ""
}
@@ -151,7 +185,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
}
+304 -7
View File
@@ -1,17 +1,40 @@
package cluster
import (
"bytes"
"path/filepath"
"git.jpi.io/amery/jpictl/pkg/rings"
)
var (
_ MachineIterator = (*Region)(nil)
_ ZoneIterator = (*Region)(nil)
_ RegionIterator = (*Zone)(nil)
_ RegionIterator = (*Cluster)(nil)
)
// A RegionIterator is a set of Regions we can iterate on
type RegionIterator interface {
ForEachRegion(func(*Region) bool)
}
// Region represents a group of zones geographically related
type Region struct {
m *Cluster
zones []*Zone
Name string
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
@@ -68,7 +91,7 @@ func (m *Cluster) initRegions(_ *ScanOptions) error {
// bind first level regions and their zones
for name, zones := range regions {
m.syncRegions(name, zones...)
m.setRegionZones(name, zones...)
}
// and combine zones to produce larger regions
@@ -78,11 +101,15 @@ func (m *Cluster) initRegions(_ *ScanOptions) error {
}
m.sortRegions()
m.scanRegionID()
m.computeZonesRegion()
return nil
}
func (m *Cluster) syncRegions(name string, zones ...*Zone) {
for _, r := range m.Regions {
func (m *Cluster) setRegionZones(name string, zones ...*Zone) {
for i := range m.Regions {
r := &m.Regions[i]
if r.Name == name {
// found
r.m = m
@@ -99,6 +126,80 @@ func (m *Cluster) syncRegions(name string, zones ...*Zone) {
})
}
func (m *Cluster) setRegionClusterToken(name string, token string) error {
for i := range m.Regions {
r := &m.Regions[i]
if r.Name == name {
// found
r.Cluster = &token
return nil
}
}
// new
m.Regions = append(m.Regions, Region{
m: m,
Name: name,
Cluster: &token,
})
return nil
}
func (m *Cluster) appendRegionRegions(name string, subs ...string) {
for i := range m.Regions {
r := &m.Regions[i]
if name == r.Name {
// found
r.Regions = append(r.Regions, subs...)
return
}
}
// new
m.Regions = append(m.Regions, Region{
Name: name,
Regions: subs,
})
}
// ForEachRegion calls a function on all regions this zone belongs to.
func (z *Zone) ForEachRegion(fn func(*Region) bool) {
if fn == nil {
return
}
z.zones.ForEachRegion(func(r *Region) bool {
var match bool
r.ForEachZone(func(z2 *Zone) bool {
match = (z == z2)
return match
})
if match && fn(r) {
return true
}
return false
})
}
func (z *Zone) appendRegions(regions ...string) error {
for _, s := range regions {
// TODO: validate
z.debug().
WithField("zone", z.Name).
WithField("region", s).
Print("attached")
z.Regions = append(z.Regions, s)
}
return nil
}
func (m *Cluster) finishRegion(r *Region) {
if r.m != nil {
// ready
@@ -108,7 +209,7 @@ func (m *Cluster) finishRegion(r *Region) {
r.m = m
sub := []string{}
for _, name := range r.Regions {
r2, ok := m.getRegion(name)
r2, ok := m.getFinishRegion(name)
if !ok {
m.warn(nil).WithField("region", name).Print("unknown region")
continue
@@ -120,15 +221,211 @@ 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]
if name == r.Name {
m.finishRegion(r)
return r, true
}
}
return nil, false
}
func (m *Cluster) getFinishRegion(name string) (*Region, bool) {
if r, ok := m.getRegion(name); ok {
m.finishRegion(r)
return r, true
}
return nil, false
}
// SyncRegions writes to the file system the regions this [Zone]
// belongs to.
func (z *Zone) SyncRegions() error {
err := z.syncZoneRegions()
if err == nil {
z.ForEachMachine(func(p *Machine) bool {
err = z.syncMachineRegions(p)
return err != nil
})
}
return err
}
func (*Zone) syncMachineRegions(p *Machine) error {
var err error
if p.IsActive() {
err = p.RemoveFile("region")
} else {
err = p.WriteStringFile("none\n", "region")
}
if err == nil {
err = p.RemoveFile(RegionClusterTokenFileName)
}
return err
}
func (z *Zone) syncZoneRegions() error {
name := filepath.Join(z.Name, ZoneRegionsFileName)
if len(z.Regions) > 0 {
var buf bytes.Buffer
for _, s := range z.Regions {
_, _ = buf.WriteString(s)
_, _ = buf.WriteRune('\n')
}
return z.zones.WriteStringFile(buf.String(), name)
}
return z.zones.RemoveFile(name)
}
// SyncRegions writes to the file system the regions covered
// by this meta-region
func (r *Region) SyncRegions() error {
if err := r.syncRegionsFile(); err != nil {
return err
}
return r.syncClusterFile()
}
func (r *Region) mkdir() error {
return r.m.MkdirAll(r.Name)
}
func (r *Region) syncRegionsFile() error {
var err error
name := filepath.Join(r.Name, ZoneRegionsFileName)
if len(r.Regions) == 0 {
err = r.m.RemoveFile(name)
} else if err = r.mkdir(); err == nil {
var buf bytes.Buffer
for _, s := range r.Regions {
_, _ = buf.WriteString(s)
_, _ = buf.WriteRune('\n')
}
err = r.m.WriteStringFile(buf.String(), name)
}
return err
}
func (r *Region) syncClusterFile() error {
var err error
name := filepath.Join(r.Name, RegionClusterTokenFileName)
if r.Cluster == nil {
err = r.m.RemoveFile(name)
} else if err = r.mkdir(); err == nil {
var buf bytes.Buffer
_, _ = buf.WriteString(*r.Cluster)
if buf.Len() > 0 {
_, _ = buf.WriteRune('\n')
}
err = r.m.WriteStringFile(buf.String(), name)
}
return err
}
+104 -108
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(),
@@ -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 {
+25 -3
View File
@@ -6,6 +6,7 @@ func (m *Cluster) SyncAll() error {
m.SyncMkdirAll,
m.SyncAllWireguard,
m.SyncAllCeph,
m.SyncAllRegions,
m.WriteHosts,
} {
if err := fn(); err != nil {
@@ -34,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
}
@@ -58,3 +59,24 @@ func (m *Cluster) SyncAllCeph() error {
return m.WriteCephConfig(cfg)
}
// SyncAllRegions rewrites all region data
func (m *Cluster) SyncAllRegions() error {
var err error
m.ForEachZone(func(z *Zone) bool {
err := z.SyncRegions()
return err != nil
})
if err != nil {
return err
}
m.ForEachRegion(func(r *Region) bool {
err = r.SyncRegions()
return 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
}
}
+77
View File
@@ -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
}
+178
View File
@@ -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)
}
}
}
+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)
}
}
}
+145
View File
@@ -0,0 +1,145 @@
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
}
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)
}
+63
View File
@@ -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)
}
}
}
+116
View File
@@ -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))
}
}
+9 -112
View File
@@ -2,7 +2,6 @@ package wireguard
import (
"bytes"
"errors"
"fmt"
"io"
"net/netip"
@@ -10,8 +9,8 @@ import (
"strings"
"text/template"
"asciigoat.org/ini/basic"
"darvaza.org/core"
"gopkg.in/gcfg.v1"
)
var configTemplate = template.Must(template.New("config").Funcs(template.FuncMap{
@@ -132,100 +131,6 @@ func (ep *EndpointAddress) FromString(s string) error {
return nil
}
type intermediateConfig struct {
Interface interfaceConfig
Peer peersConfig
}
func (v *intermediateConfig) Export() (*Config, error) {
var out Config
var err error
// Interface
out.Interface, err = v.Interface.Export()
if err != nil {
return nil, err
}
// Peers
peers, ok := v.PeersCount()
if !ok {
return nil, errors.New("inconsistent Peer data")
}
for i := 0; i < peers; i++ {
p, err := v.ExportPeer(i)
if err != nil {
err = core.Wrap(err, "Peer[%v]:", i)
return nil, err
}
out.Peer = append(out.Peer, p)
}
return &out, nil
}
type interfaceConfig struct {
Address netip.Addr
PrivateKey string
ListenPort uint16
}
func (p interfaceConfig) Export() (InterfaceConfig, error) {
var err error
out := InterfaceConfig{
Address: p.Address,
ListenPort: p.ListenPort,
}
if p.PrivateKey != "" {
out.PrivateKey, err = PrivateKeyFromBase64(p.PrivateKey)
if err != nil {
err = core.Wrap(err, "PrivateKey")
return InterfaceConfig{}, err
}
}
return out, nil
}
type peersConfig struct {
PublicKey []string
Endpoint []string
AllowedIPs []string
}
func (v *intermediateConfig) ExportPeer(i int) (PeerConfig, error) {
var out PeerConfig
// Endpoint
s := v.Peer.Endpoint[i]
err := out.Endpoint.FromString(s)
if err != nil {
err = core.Wrap(err, "Endpoint")
return out, err
}
// PublicKey
out.PublicKey, err = PublicKeyFromBase64(v.Peer.PublicKey[i])
if err != nil {
err = core.Wrap(err, "PublicKey")
return out, err
}
// AllowedIPs
s = v.Peer.AllowedIPs[i]
out.AllowedIPs, err = parseAllowedIPs(s)
if err != nil {
err = core.Wrap(err, "AllowedIPs")
return out, err
}
return out, nil
}
func parseAllowedIPs(data string) ([]netip.Prefix, error) {
var out []netip.Prefix
@@ -242,25 +147,17 @@ func parseAllowedIPs(data string) ([]netip.Prefix, error) {
return out, nil
}
func (v *intermediateConfig) PeersCount() (int, bool) {
c0 := len(v.Peer.Endpoint)
c1 := len(v.Peer.PublicKey)
c2 := len(v.Peer.AllowedIPs)
if c0 != c1 || c1 != c2 {
return 0, false
}
return c0, true
}
// NewConfigFromReader parses a wgN.conf file
func NewConfigFromReader(r io.Reader) (*Config, error) {
temp := &intermediateConfig{}
if err := gcfg.ReadInto(temp, r); err != nil {
doc, err := basic.Decode(r)
if err != nil {
return nil, err
}
return temp.Export()
cfg, err := newConfigFromDocument(doc)
if err != nil {
return nil, err
}
return cfg, nil
}
+169
View File
@@ -0,0 +1,169 @@
package wireguard
import (
"io/fs"
"strconv"
"asciigoat.org/ini/basic"
"darvaza.org/core"
)
type sectionHandler func(*Config, *basic.Section) error
var sectionMap = map[string]func(*Config, *basic.Section) error{
"Interface": loadInterfaceConfSection,
"Peer": loadPeerConfSection,
}
func loadConfSection(out *Config, src *basic.Section) error {
h, ok := sectionMap[src.Key]
if !ok {
return core.Wrap(fs.ErrInvalid, "unknown section %q", src.Key)
}
return h(out, src)
}
func loadInterfaceConfSection(out *Config, src *basic.Section) error {
var cfg InterfaceConfig
for _, field := range src.Fields {
if err := loadInterfaceConfField(&cfg, field); err != nil {
return core.Wrap(err, "Interface")
}
}
out.Interface = cfg
return nil
}
func loadPeerConfSection(out *Config, src *basic.Section) error {
var cfg PeerConfig
for _, field := range src.Fields {
if err := loadPeerConfField(&cfg, field); err != nil {
return core.Wrap(err, "Peer[%v]", len(out.Peer))
}
}
out.Peer = append(out.Peer, cfg)
return nil
}
// revive:disable:cyclomatic
// revive:disable:cognitive-complexity
func loadInterfaceConfField(cfg *InterfaceConfig, field basic.Field) error {
// revive:enable:cyclomatic
// revive:enable:cognitive-complexity
// TODO: refactor when asciigoat's ini parser learns to do reflection
switch field.Key {
case "Address":
if !core.IsZero(cfg.Address) {
return core.Wrap(fs.ErrInvalid, "duplicate field %q", field.Key)
}
err := cfg.Address.UnmarshalText([]byte(field.Value))
switch {
case err != nil:
return core.Wrap(err, field.Key)
default:
return nil
}
case "PrivateKey":
if !core.IsZero(cfg.PrivateKey) {
return core.Wrap(fs.ErrInvalid, "duplicate field %q", field.Key)
}
err := cfg.PrivateKey.UnmarshalText([]byte(field.Value))
switch {
case err != nil:
return core.Wrap(err, field.Key)
default:
return nil
}
case "ListenPort":
if cfg.ListenPort > 0 {
return core.Wrap(fs.ErrInvalid, "duplicate field %q", field.Key)
}
u64, err := strconv.ParseUint(field.Value, 10, 16)
switch {
case err != nil:
return core.Wrap(err, field.Key)
case u64 == 0:
return core.Wrap(fs.ErrInvalid, "invalid %q value", field.Key)
default:
cfg.ListenPort = uint16(u64)
return nil
}
default:
return core.Wrap(fs.ErrInvalid, "unknown field %q", field.Key)
}
}
// revive:disable:cyclomatic
// revive:disable:cognitive-complexity
func loadPeerConfField(cfg *PeerConfig, field basic.Field) error {
// revive:enable:cyclomatic
// revive:enable:cognitive-complexity
switch field.Key {
case "PublicKey":
if !core.IsZero(cfg.PublicKey) {
return core.Wrap(fs.ErrInvalid, "duplicate field %q", field.Key)
}
err := cfg.PublicKey.UnmarshalText([]byte(field.Value))
switch {
case err != nil:
return core.Wrap(err, field.Key)
default:
return nil
}
case "Endpoint":
if cfg.Endpoint.String() != "" {
return core.Wrap(fs.ErrInvalid, "duplicate field %q", field.Key)
}
err := cfg.Endpoint.UnmarshalText([]byte(field.Value))
switch {
case err != nil:
return core.Wrap(err, field.Key)
default:
return nil
}
case "AllowedIPs":
s, err := parseAllowedIPs(field.Value)
switch {
case err != nil:
return core.Wrap(err, field.Key)
case len(s) > 0:
cfg.AllowedIPs = append(cfg.AllowedIPs, s...)
return nil
}
default:
return core.Wrap(fs.ErrInvalid, "unknown field %q", field.Key)
}
return nil
}
func newConfigFromDocument(doc *basic.Document) (*Config, error) {
var out Config
if len(doc.Global) > 0 {
err := core.Wrap(fs.ErrInvalid, "fields before the first section")
return nil, err
}
for i := range doc.Sections {
src := &doc.Sections[i]
if err := loadConfSection(&out, src); err != nil {
return nil, err
}
}
return &out, nil
}