diff --git a/cmd/jpictl/config.go b/cmd/jpictl/config.go index d58439d..a7649fc 100644 --- a/cmd/jpictl/config.go +++ b/cmd/jpictl/config.go @@ -1,6 +1,6 @@ package main -import "git.jpi.io/amery/jpictl/pkg/zones" +import "git.jpi.io/amery/jpictl/pkg/cluster" // Config describes the repository type Config struct { @@ -9,14 +9,14 @@ type Config struct { } var cfg = &Config{ - Base: "./m", - Domain: "m.jpi.cloud", + Base: "m", + Domain: "jpi.cloud", } // LoadZones loads all zones and machines in the config directory -func (cfg *Config) LoadZones(resolve bool) (*zones.Zones, error) { - return zones.New(cfg.Base, cfg.Domain, - zones.ResolvePublicAddresses(resolve), - zones.WithLogger(log), +func (cfg *Config) LoadZones(resolve bool) (*cluster.Cluster, error) { + return cluster.NewFromDirectory(cfg.Base, cfg.Domain, + cluster.ResolvePublicAddresses(resolve), + cluster.WithLogger(log), ) } diff --git a/cmd/jpictl/gateway.go b/cmd/jpictl/gateway.go index 41b869b..a2489e3 100644 --- a/cmd/jpictl/gateway.go +++ b/cmd/jpictl/gateway.go @@ -7,8 +7,9 @@ import ( "strconv" "strings" - "git.jpi.io/amery/jpictl/pkg/zones" "github.com/spf13/cobra" + + "git.jpi.io/amery/jpictl/pkg/cluster" ) // Command @@ -38,9 +39,9 @@ var gatewaySetCmd = &cobra.Command{ }, } -func gatewaySet(zi zones.ZoneIterator, gw string) error { +func gatewaySet(zi cluster.ZoneIterator, gw string) error { var err error - zi.ForEachZone(func(z *zones.Zone) bool { + zi.ForEachZone(func(z *cluster.Zone) bool { for _, m := range z.Machines { if m.Name == gw { z.SetGateway(m.ID, true) @@ -74,9 +75,9 @@ var gatewayUnsetCmd = &cobra.Command{ }, } -func gatewayUnset(zi zones.ZoneIterator, ngw string) error { +func gatewayUnset(zi cluster.ZoneIterator, ngw string) error { var err error - zi.ForEachZone(func(z *zones.Zone) bool { + zi.ForEachZone(func(z *cluster.Zone) bool { for _, m := range z.Machines { if m.Name == ngw && m.IsGateway() { z.SetGateway(m.ID, false) @@ -115,10 +116,10 @@ var gatewayListCmd = &cobra.Command{ }, } -func gatewayListAll(zi zones.ZoneIterator) error { +func gatewayListAll(zi cluster.ZoneIterator) error { var b bytes.Buffer var err error - zi.ForEachZone(func(z *zones.Zone) bool { + zi.ForEachZone(func(z *cluster.Zone) bool { b.WriteString(z.Name + ":") var sIDs []string ids, num := z.GatewayIDs() @@ -137,10 +138,10 @@ func gatewayListAll(zi zones.ZoneIterator) error { return err } -func gatewayList(zi zones.ZoneIterator, m string) error { +func gatewayList(zi cluster.ZoneIterator, m string) error { var b bytes.Buffer var err error - zi.ForEachZone(func(z *zones.Zone) bool { + zi.ForEachZone(func(z *cluster.Zone) bool { if z.Name == m { b.WriteString(z.Name + ":") ids, num := z.GatewayIDs() diff --git a/pkg/zones/ceph.go b/pkg/cluster/ceph.go similarity index 89% rename from pkg/zones/ceph.go rename to pkg/cluster/ceph.go index 676339b..b91b98b 100644 --- a/pkg/zones/ceph.go +++ b/pkg/cluster/ceph.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "bytes" @@ -12,7 +12,7 @@ import ( ) // GetCephFSID returns our Ceph's FSID -func (m *Zones) GetCephFSID() (uuid.UUID, error) { +func (m *Cluster) GetCephFSID() (uuid.UUID, error) { if core.IsZero(m.CephFSID) { // generate one v, err := uuid.NewV4() @@ -25,7 +25,7 @@ func (m *Zones) GetCephFSID() (uuid.UUID, error) { } // GetCephConfig reads the ceph.conf file -func (m *Zones) GetCephConfig() (*ceph.Config, error) { +func (m *Cluster) GetCephConfig() (*ceph.Config, error) { data, err := m.ReadFile("ceph.conf") if err != nil { return nil, err @@ -36,7 +36,7 @@ func (m *Zones) GetCephConfig() (*ceph.Config, error) { } // WriteCephConfig writes the ceph.conf file -func (m *Zones) WriteCephConfig(cfg *ceph.Config) error { +func (m *Cluster) WriteCephConfig(cfg *ceph.Config) error { f, err := m.CreateTruncFile("ceph.conf") if err != nil { return err @@ -48,7 +48,7 @@ func (m *Zones) WriteCephConfig(cfg *ceph.Config) error { } // GenCephConfig prepares a ceph.Config using the cluster information -func (m *Zones) GenCephConfig() (*ceph.Config, error) { +func (m *Cluster) GenCephConfig() (*ceph.Config, error) { fsid, err := m.GetCephFSID() if err != nil { return nil, err diff --git a/pkg/zones/ceph_scan.go b/pkg/cluster/ceph_scan.go similarity index 96% rename from pkg/zones/ceph_scan.go rename to pkg/cluster/ceph_scan.go index 7cb4f37..f85d09f 100644 --- a/pkg/zones/ceph_scan.go +++ b/pkg/cluster/ceph_scan.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "os" @@ -71,7 +71,7 @@ func newCephScanTODO(cfg *ceph.Config) *cephScanTODO { return todo } -func (m *Zones) scanCephMonitors(_ *ScanOptions) error { +func (m *Cluster) scanCephMonitors(_ *ScanOptions) error { cfg, err := m.GetCephConfig() switch { case os.IsNotExist(err): diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go new file mode 100644 index 0000000..654333a --- /dev/null +++ b/pkg/cluster/cluster.go @@ -0,0 +1,77 @@ +// Package cluster contains information about the cluster +package cluster + +import ( + "io/fs" + + "darvaza.org/resolver" + "darvaza.org/slog" + "github.com/gofrs/uuid/v5" +) + +var ( + _ MachineIterator = (*Cluster)(nil) + _ ZoneIterator = (*Cluster)(nil) +) + +// revive:disable:line-length-limit + +// Cluster represents all zones in a cluster +type Cluster struct { + dir fs.FS + log slog.Logger + resolver resolver.Resolver + + BaseDir string `json:"dir,omitempty" yaml:"dir,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Domain string `json:"domain,omitempty" yaml:"domain,omitempty"` + + CephFSID uuid.UUID `json:"ceph_fsid,omitempty" yaml:"ceph_fsid,omitempty"` + Zones []*Zone `json:"zones,omitempty" yaml:"zones,omitempty"` +} + +// revive:enable:line-length-limit + +// ForEachMachine calls a function for each Machine in the cluster +// until instructed to terminate the loop +func (m *Cluster) ForEachMachine(fn func(*Machine) bool) { + m.ForEachZone(func(z *Zone) bool { + var term bool + + z.ForEachMachine(func(p *Machine) bool { + term = fn(p) + return term + }) + + return term + }) +} + +// ForEachZone calls a function for each Zone in the cluster +// until instructed to terminate the loop +func (m *Cluster) ForEachZone(fn func(*Zone) bool) { + for _, p := range m.Zones { + if fn(p) { + // terminate + return + } + } +} + +// GetMachineByName looks for a machine with the specified +// name on any zone +func (m *Cluster) GetMachineByName(name string) (*Machine, bool) { + var out *Machine + + if name != "" { + m.ForEachMachine(func(p *Machine) bool { + if p.Name == name { + out = p + } + + return out != nil + }) + } + + return out, out != nil +} diff --git a/pkg/zones/zones_file.go b/pkg/cluster/cluster_file.go similarity index 65% rename from pkg/zones/zones_file.go rename to pkg/cluster/cluster_file.go index b711163..306b3f2 100644 --- a/pkg/zones/zones_file.go +++ b/pkg/cluster/cluster_file.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "fmt" @@ -9,7 +9,7 @@ import ( ) // OpenFile opens a file on the cluster's config directory with the specified flags -func (m *Zones) OpenFile(name string, flags int, args ...any) (fs.File, error) { +func (m *Cluster) OpenFile(name string, flags int, args ...any) (fs.File, error) { if len(args) > 0 { name = fmt.Sprintf(name, args...) } @@ -18,16 +18,16 @@ func (m *Zones) OpenFile(name string, flags int, args ...any) (fs.File, error) { } // CreateTruncFile creates or truncates a file on the cluster's config directory -func (m *Zones) CreateTruncFile(name string, args ...any) (io.WriteCloser, error) { +func (m *Cluster) CreateTruncFile(name string, args ...any) (io.WriteCloser, error) { return m.openWriter(name, os.O_CREATE|os.O_TRUNC, args...) } // CreateFile creates a file on the cluster's config directory -func (m *Zones) CreateFile(name string, args ...any) (io.WriteCloser, error) { +func (m *Cluster) CreateFile(name string, args ...any) (io.WriteCloser, error) { return m.openWriter(name, os.O_CREATE, args...) } -func (m *Zones) openWriter(name string, flags int, args ...any) (io.WriteCloser, error) { +func (m *Cluster) openWriter(name string, flags int, args ...any) (io.WriteCloser, error) { f, err := m.OpenFile(name, os.O_WRONLY|flags, args...) if err != nil { return nil, err @@ -37,7 +37,7 @@ func (m *Zones) openWriter(name string, flags int, args ...any) (io.WriteCloser, } // ReadFile reads a file from the cluster's config directory -func (m *Zones) ReadFile(name string, args ...any) ([]byte, error) { +func (m *Cluster) ReadFile(name string, args ...any) ([]byte, error) { if len(args) > 0 { name = fmt.Sprintf(name, args...) } diff --git a/pkg/zones/scan.go b/pkg/cluster/cluster_scan.go similarity index 88% rename from pkg/zones/scan.go rename to pkg/cluster/cluster_scan.go index c7b4eef..d148674 100644 --- a/pkg/zones/scan.go +++ b/pkg/cluster/cluster_scan.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "io/fs" @@ -7,7 +7,7 @@ import ( "darvaza.org/core" ) -func (m *Zones) scan(opts *ScanOptions) error { +func (m *Cluster) scan(opts *ScanOptions) error { for _, fn := range []func(*ScanOptions) error{ m.scanDirectory, m.scanMachines, @@ -24,7 +24,7 @@ func (m *Zones) scan(opts *ScanOptions) error { return nil } -func (m *Zones) scanDirectory(_ *ScanOptions) error { +func (m *Cluster) scanDirectory(_ *ScanOptions) error { // each directory is a zone entries, err := fs.ReadDir(m.dir, ".") if err != nil { @@ -50,7 +50,7 @@ func (m *Zones) scanDirectory(_ *ScanOptions) error { return nil } -func (m *Zones) newZone(name string) (*Zone, error) { +func (m *Cluster) newZone(name string) (*Zone, error) { z := &Zone{ zones: m, logger: m, @@ -67,7 +67,7 @@ func (m *Zones) newZone(name string) (*Zone, error) { return z, nil } -func (m *Zones) scanMachines(opts *ScanOptions) error { +func (m *Cluster) scanMachines(opts *ScanOptions) error { var err error m.ForEachMachine(func(p *Machine) bool { err = p.scan(opts) @@ -76,7 +76,7 @@ func (m *Zones) scanMachines(opts *ScanOptions) error { return err } -func (m *Zones) scanZoneIDs(_ *ScanOptions) error { +func (m *Cluster) scanZoneIDs(_ *ScanOptions) error { var hasMissing bool var lastZoneID int @@ -106,7 +106,7 @@ func (m *Zones) scanZoneIDs(_ *ScanOptions) error { return nil } -func (m *Zones) scanSort(_ *ScanOptions) error { +func (m *Cluster) scanSort(_ *ScanOptions) error { sort.SliceStable(m.Zones, func(i, j int) bool { id1 := m.Zones[i].ID id2 := m.Zones[j].ID @@ -132,7 +132,7 @@ func (m *Zones) scanSort(_ *ScanOptions) error { return nil } -func (m *Zones) scanGateways(_ *ScanOptions) error { +func (m *Cluster) scanGateways(_ *ScanOptions) error { var err error m.ForEachZone(func(z *Zone) bool { diff --git a/pkg/zones/options.go b/pkg/cluster/cluster_scan_options.go similarity index 65% rename from pkg/zones/options.go rename to pkg/cluster/cluster_scan_options.go index e7a66f2..53d2a28 100644 --- a/pkg/zones/options.go +++ b/pkg/cluster/cluster_scan_options.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "io/fs" @@ -6,12 +6,11 @@ import ( "darvaza.org/resolver" "darvaza.org/slog" - "darvaza.org/slog/handlers/discard" "github.com/hack-pad/hackpadfs/os" ) // A ScanOption pre-configures the Zones before scanning -type ScanOption func(*Zones, *ScanOptions) error +type ScanOption func(*Cluster, *ScanOptions) error // ScanOptions contains flags used by the initial scan type ScanOptions struct { @@ -29,7 +28,7 @@ type ScanOptions struct { // the DNS resolver to get PublicAddresses of nodes. // Default is true func ResolvePublicAddresses(resolve bool) ScanOption { - return func(m *Zones, opt *ScanOptions) error { + return func(m *Cluster, opt *ScanOptions) error { opt.DontResolvePublicAddresses = !resolve return nil } @@ -38,7 +37,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 *Zones, opt *ScanOptions) error { + return func(m *Cluster, opt *ScanOptions) error { if h == nil { return fs.ErrInvalid } @@ -51,7 +50,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 *Zones, opt *ScanOptions) error { + return func(m *Cluster, opt *ScanOptions) error { if h == nil { h = resolver.SystemResolver(true) } @@ -63,9 +62,9 @@ func WithResolver(h resolver.Resolver) ScanOption { // WithLogger specifies what to use for logging func WithLogger(log slog.Logger) ScanOption { - return func(m *Zones, opt *ScanOptions) error { + return func(m *Cluster, opt *ScanOptions) error { if log == nil { - log = discard.New() + log = DefaultLogger() } opt.Logger = log @@ -74,9 +73,9 @@ func WithLogger(log slog.Logger) ScanOption { } } -func (m *Zones) setDefaults(opt *ScanOptions) error { +func (m *Cluster) setScanDefaults(opt *ScanOptions) error { if m.resolver == nil { - h := resolver.NewCloudflareLookuper() + h := DefaultLookuper() if err := WithLookuper(h)(m, opt); err != nil { return err @@ -92,43 +91,41 @@ func (m *Zones) setDefaults(opt *ScanOptions) error { return nil } -// NewFS builds a [Zones] tree using the given directory -func NewFS(dir fs.FS, domain string, opts ...ScanOption) (*Zones, error) { +// NewFromDirectory builds a [Cluster] tree using the given directory +func NewFromDirectory(dir, domain string, opts ...ScanOption) (*Cluster, error) { var scanOptions ScanOptions - z := &Zones{ - dir: dir, - domain: domain, - } - - for _, opt := range opts { - if err := opt(z, &scanOptions); err != nil { - return nil, err - } + dir = filepath.Clean(dir) + fullPath, err := filepath.Abs(dir) + if err != nil { + return nil, err } - if err := z.setDefaults(&scanOptions); err != nil { + sub, err := os.NewFS().Sub(fullPath[1:]) + if err != nil { return nil, err } - if err := z.scan(&scanOptions); err != nil { - return nil, err + m := &Cluster{ + dir: sub, + BaseDir: dir, + Name: filepath.Base(fullPath), + Domain: domain, } - return z, nil -} + for _, opt := range opts { + if err := opt(m, &scanOptions); err != nil { + return nil, err + } + } -// New builds a [Zones] tree using the given directory -func New(dir, domain string, opts ...ScanOption) (*Zones, error) { - dir, err := filepath.Abs(dir) - if err != nil { + if err := m.setScanDefaults(&scanOptions); err != nil { return nil, err } - base, err := os.NewFS().Sub(dir[1:]) - if err != nil { + if err := m.scan(&scanOptions); err != nil { return nil, err } - return NewFS(base, domain, opts...) + return m, nil } diff --git a/pkg/cluster/defaults.go b/pkg/cluster/defaults.go new file mode 100644 index 0000000..925b3d5 --- /dev/null +++ b/pkg/cluster/defaults.go @@ -0,0 +1,17 @@ +package cluster + +import ( + "darvaza.org/resolver" + "darvaza.org/slog" + "darvaza.org/slog/handlers/discard" +) + +// DefaultLogger returns a logger that doesn't log anything +func DefaultLogger() slog.Logger { + return discard.New() +} + +// DefaultLookuper returns a [resolver.Lookuper] using Cloudflare's 1.1.1.1 +func DefaultLookuper() resolver.Lookuper { + return resolver.NewCloudflareLookuper() +} diff --git a/pkg/zones/env.go b/pkg/cluster/env.go similarity index 98% rename from pkg/zones/env.go rename to pkg/cluster/env.go index 8780da9..cf5b171 100644 --- a/pkg/zones/env.go +++ b/pkg/cluster/env.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "bytes" @@ -16,7 +16,7 @@ type Env struct { } // Env returns a shell environment factory -func (m *Zones) Env(export bool) (*Env, error) { +func (m *Cluster) Env(export bool) (*Env, error) { fsid, err := m.GetCephFSID() if err != nil { return nil, err diff --git a/pkg/zones/errors.go b/pkg/cluster/errors.go similarity index 96% rename from pkg/zones/errors.go rename to pkg/cluster/errors.go index 6fd068f..9035d36 100644 --- a/pkg/zones/errors.go +++ b/pkg/cluster/errors.go @@ -1,4 +1,4 @@ -package zones +package cluster import "errors" diff --git a/pkg/zones/log.go b/pkg/cluster/log.go similarity index 62% rename from pkg/zones/log.go rename to pkg/cluster/log.go index ceaf5ee..7f01677 100644 --- a/pkg/zones/log.go +++ b/pkg/cluster/log.go @@ -1,4 +1,4 @@ -package zones +package cluster import "darvaza.org/slog" @@ -13,26 +13,26 @@ type logger interface { } var ( - _ logger = (*Zones)(nil) + _ logger = (*Cluster)(nil) ) -func (z *Zones) withDebug() (slog.Logger, bool) { +func (z *Cluster) withDebug() (slog.Logger, bool) { return z.debug().WithEnabled() } -func (z *Zones) withInfo() (slog.Logger, bool) { +func (z *Cluster) withInfo() (slog.Logger, bool) { return z.debug().WithEnabled() } -func (z *Zones) debug() slog.Logger { +func (z *Cluster) debug() slog.Logger { return z.log.Debug() } -func (z *Zones) info() slog.Logger { +func (z *Cluster) info() slog.Logger { return z.log.Info() } -func (z *Zones) warn(err error) slog.Logger { +func (z *Cluster) warn(err error) slog.Logger { l := z.log.Warn() if err != nil { l = l.WithField(slog.ErrorFieldName, err) @@ -40,7 +40,7 @@ func (z *Zones) warn(err error) slog.Logger { return l } -func (z *Zones) error(err error) slog.Logger { +func (z *Cluster) error(err error) slog.Logger { l := z.log.Error() if err != nil { l = l.WithField(slog.ErrorFieldName, err) diff --git a/pkg/zones/machine.go b/pkg/cluster/machine.go similarity index 87% rename from pkg/zones/machine.go rename to pkg/cluster/machine.go index e8f3dbe..46ac7e9 100644 --- a/pkg/zones/machine.go +++ b/pkg/cluster/machine.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "net/netip" @@ -28,16 +28,19 @@ func (m *Machine) String() string { // FullName returns the Name of the machine including domain name func (m *Machine) FullName() string { - if domain := m.zone.zones.domain; domain != "" { - var s = []string{ - m.Name, - domain, + var name []string + + for _, s := range []string{ + m.Name, + m.zone.zones.Name, + m.zone.zones.Domain, + } { + if s != "" { + name = append(name, s) } - - return strings.Join(s, ".") } - return m.Name + return strings.Join(name, ".") } // IsGateway tells if the Machine is a ring0 gateway diff --git a/pkg/zones/machine_file.go b/pkg/cluster/machine_file.go similarity index 99% rename from pkg/zones/machine_file.go rename to pkg/cluster/machine_file.go index 6fd0eac..a6e90eb 100644 --- a/pkg/zones/machine_file.go +++ b/pkg/cluster/machine_file.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "bytes" diff --git a/pkg/zones/machine_rings.go b/pkg/cluster/machine_rings.go similarity index 99% rename from pkg/zones/machine_rings.go rename to pkg/cluster/machine_rings.go index 4baaaee..cffcd4f 100644 --- a/pkg/zones/machine_rings.go +++ b/pkg/cluster/machine_rings.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "bytes" diff --git a/pkg/zones/machine_scan.go b/pkg/cluster/machine_scan.go similarity index 99% rename from pkg/zones/machine_scan.go rename to pkg/cluster/machine_scan.go index cb5a1be..e282800 100644 --- a/pkg/zones/machine_scan.go +++ b/pkg/cluster/machine_scan.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "context" diff --git a/pkg/cluster/machines.go b/pkg/cluster/machines.go new file mode 100644 index 0000000..e49f2d5 --- /dev/null +++ b/pkg/cluster/machines.go @@ -0,0 +1,69 @@ +package cluster + +import "sort" + +var ( + _ MachineIterator = Machines(nil) + _ sort.Interface = Machines(nil) +) + +// A MachineIterator is a set of Machines we can iterate on +type MachineIterator interface { + ForEachMachine(func(*Machine) bool) +} + +// Machines is a list of Machine objects +type Machines []*Machine + +// ForEachMachine calls a function for each Machine in the list +// until instructed to terminate the loop +func (m Machines) ForEachMachine(fn func(*Machine) bool) { + for _, p := range m { + if fn(p) { + return + } + } +} + +// Len returns the number of machines in the list +func (m Machines) Len() int { + return len(m) +} + +// Less implements sort.Interface to sort the list +func (m Machines) Less(i, j int) bool { + a, b := m[i], m[j] + za, zb := a.Zone(), b.Zone() + + switch { + case za == zb: + return a.ID < b.ID + default: + return za < zb + } +} + +// Swap implements sort.Interface to sort the list +func (m Machines) Swap(i, j int) { + m[i], m[j] = m[j], m[i] +} + +// FilterMachines produces a subset of the machines offered by the given +// iterator fulfilling a condition +func FilterMachines(m MachineIterator, cond func(*Machine) bool) (Machines, int) { + var out []*Machine + + if cond == nil { + // unconditional + cond = func(*Machine) bool { return true } + } + + m.ForEachMachine(func(p *Machine) bool { + if cond(p) { + out = append(out, p) + } + return false + }) + + return out, len(out) +} diff --git a/pkg/zones/rings.go b/pkg/cluster/rings.go similarity index 99% rename from pkg/zones/rings.go rename to pkg/cluster/rings.go index 3a88a8e..64cae1c 100644 --- a/pkg/zones/rings.go +++ b/pkg/cluster/rings.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "fmt" diff --git a/pkg/zones/sync.go b/pkg/cluster/sync.go similarity index 81% rename from pkg/zones/sync.go rename to pkg/cluster/sync.go index b7c8b89..1c3fc2b 100644 --- a/pkg/zones/sync.go +++ b/pkg/cluster/sync.go @@ -1,7 +1,7 @@ -package zones +package cluster // SyncAll updates all config files -func (m *Zones) SyncAll() error { +func (m *Cluster) SyncAll() error { for _, fn := range []func() error{ m.SyncAllWireguard, m.SyncAllCeph, @@ -15,7 +15,7 @@ func (m *Zones) SyncAll() error { } // SyncAllWireguard updates all wireguard config files -func (m *Zones) SyncAllWireguard() error { +func (m *Cluster) SyncAllWireguard() error { var err error for ring := 0; ring < RingsCount; ring++ { @@ -34,7 +34,7 @@ func (m *Zones) SyncAllWireguard() error { } // SyncAllCeph updates the ceph.conf file -func (m *Zones) SyncAllCeph() error { +func (m *Cluster) SyncAllCeph() error { cfg, err := m.GenCephConfig() if err != nil { return err diff --git a/pkg/zones/wireguard.go b/pkg/cluster/wireguard.go similarity index 93% rename from pkg/zones/wireguard.go rename to pkg/cluster/wireguard.go index 35888db..b6a801a 100644 --- a/pkg/zones/wireguard.go +++ b/pkg/cluster/wireguard.go @@ -1,4 +1,4 @@ -package zones +package cluster import ( "io/fs" @@ -6,19 +6,19 @@ import ( ) var ( - _ WireguardConfigPruner = (*Zones)(nil) + _ WireguardConfigPruner = (*Cluster)(nil) _ WireguardConfigPruner = (*Zone)(nil) _ WireguardConfigPruner = (*Machine)(nil) - _ WireguardConfigWriter = (*Zones)(nil) + _ WireguardConfigWriter = (*Cluster)(nil) _ WireguardConfigWriter = (*Zone)(nil) _ WireguardConfigWriter = (*Machine)(nil) - _ WireguardConfigSyncer = (*Zones)(nil) + _ WireguardConfigSyncer = (*Cluster)(nil) _ WireguardConfigSyncer = (*Zone)(nil) _ WireguardConfigSyncer = (*Machine)(nil) - _ WireguardKeysWriter = (*Zones)(nil) + _ WireguardKeysWriter = (*Cluster)(nil) _ WireguardKeysWriter = (*Zone)(nil) _ WireguardKeysWriter = (*Machine)(nil) ) @@ -31,7 +31,7 @@ type WireguardConfigPruner interface { // PruneWireguardConfig removes wgN.conf files of machines with // the corresponding ring disabled on all zones -func (m *Zones) PruneWireguardConfig(ring int) error { +func (m *Cluster) PruneWireguardConfig(ring int) error { return pruneWireguardConfig(m, ring) } @@ -76,7 +76,7 @@ type WireguardConfigWriter interface { // WriteWireguardConfig rewrites all wgN.conf on all machines // attached to that ring -func (m *Zones) WriteWireguardConfig(ring int) error { +func (m *Cluster) WriteWireguardConfig(ring int) error { switch ring { case 0: return writeWireguardConfig(m, m, ring) @@ -154,7 +154,7 @@ type WireguardConfigSyncer interface { // SyncWireguardConfig updates all wgN.conf files for the specified // ring -func (m *Zones) SyncWireguardConfig(ring int) error { +func (m *Cluster) SyncWireguardConfig(ring int) error { switch ring { case 0: return syncWireguardConfig(m, m, ring) @@ -214,7 +214,7 @@ type WireguardKeysWriter interface { } // WriteWireguardKeys rewrites all wgN.{key,pub} files -func (m *Zones) WriteWireguardKeys(ring int) error { +func (m *Cluster) WriteWireguardKeys(ring int) error { return writeWireguardKeys(m, ring) } diff --git a/pkg/cluster/zones.go b/pkg/cluster/zones.go new file mode 100644 index 0000000..59898b9 --- /dev/null +++ b/pkg/cluster/zones.go @@ -0,0 +1,68 @@ +package cluster + +import ( + "io/fs" +) + +var ( + _ MachineIterator = (*Zone)(nil) +) + +// A ZoneIterator is a set of Zones we can iterate on +type ZoneIterator interface { + ForEachZone(func(*Zone) bool) +} + +// A Zone is a set of machines in close proximity and strong +// affinity. +type Zone struct { + zones *Cluster + logger `json:"-" yaml:"-"` + + ID int + Name string + + Machines +} + +func (z *Zone) String() string { + return z.Name +} + +// SetGateway configures a machine to be the zone's ring0 gateway +func (z *Zone) SetGateway(gatewayID int, enabled bool) error { + var err error + var found bool + + z.ForEachMachine(func(p *Machine) bool { + if p.ID == gatewayID { + found = true + err = p.SetGateway(enabled) + + return true + } + return false + }) + + switch { + case err != nil: + return err + case !found: + return fs.ErrNotExist + default: + return nil + } +} + +// GatewayIDs returns the list of IDs of machines that act as ring0 gateways +func (z *Zone) GatewayIDs() ([]int, int) { + var out []int + z.ForEachMachine(func(p *Machine) bool { + if p.IsGateway() { + out = append(out, p.ID) + } + return false + }) + + return out, len(out) +} diff --git a/pkg/zones/zones.go b/pkg/zones/zones.go deleted file mode 100644 index 952b305..0000000 --- a/pkg/zones/zones.go +++ /dev/null @@ -1,198 +0,0 @@ -// Package zones contains information about the cluster -package zones - -import ( - "io/fs" - "sort" - - "darvaza.org/resolver" - "darvaza.org/slog" - "github.com/gofrs/uuid/v5" -) - -var ( - _ MachineIterator = Machines(nil) - _ sort.Interface = Machines(nil) - - _ MachineIterator = (*Zone)(nil) - _ MachineIterator = (*Zones)(nil) - _ ZoneIterator = (*Zones)(nil) -) - -// A MachineIterator is a set of Machines we can iterate on -type MachineIterator interface { - ForEachMachine(func(*Machine) bool) -} - -// A ZoneIterator is a set of Zones we can iterate on -type ZoneIterator interface { - ForEachZone(func(*Zone) bool) -} - -// Machines is a list of Machine objects -type Machines []*Machine - -// ForEachMachine calls a function for each Machine in the list -// until instructed to terminate the loop -func (m Machines) ForEachMachine(fn func(*Machine) bool) { - for _, p := range m { - if fn(p) { - return - } - } -} - -// Len returns the number of machines in the list -func (m Machines) Len() int { - return len(m) -} - -// Less implements sort.Interface to sort the list -func (m Machines) Less(i, j int) bool { - a, b := m[i], m[j] - za, zb := a.Zone(), b.Zone() - - switch { - case za == zb: - return a.ID < b.ID - default: - return za < zb - } -} - -// Swap implements sort.Interface to sort the list -func (m Machines) Swap(i, j int) { - m[i], m[j] = m[j], m[i] -} - -// FilterMachines produces a subset of the machines offered by the given -// iterator fulfilling a condition -func FilterMachines(m MachineIterator, cond func(*Machine) bool) (Machines, int) { - var out []*Machine - - if cond == nil { - // unconditional - cond = func(*Machine) bool { return true } - } - - m.ForEachMachine(func(p *Machine) bool { - if cond(p) { - out = append(out, p) - } - return false - }) - - return out, len(out) -} - -// Zone represents one zone in a cluster -type Zone struct { - zones *Zones - logger `json:"-" yaml:"-"` - - ID int - Name string - - Machines -} - -func (z *Zone) String() string { - return z.Name -} - -// SetGateway configures a machine to be the zone's ring0 gateway -func (z *Zone) SetGateway(gatewayID int, enabled bool) error { - var err error - var found bool - - z.ForEachMachine(func(p *Machine) bool { - if p.ID == gatewayID { - found = true - err = p.SetGateway(enabled) - - return true - } - return false - }) - - switch { - case err != nil: - return err - case !found: - return fs.ErrNotExist - default: - return nil - } -} - -// GatewayIDs returns the list of IDs of machines that act as ring0 gateways -func (z *Zone) GatewayIDs() ([]int, int) { - var out []int - z.ForEachMachine(func(p *Machine) bool { - if p.IsGateway() { - out = append(out, p.ID) - } - return false - }) - - return out, len(out) -} - -// revive:disable:line-length-limit - -// Zones represents all zones in a cluster -type Zones struct { - dir fs.FS - log slog.Logger - resolver resolver.Resolver - domain string - - CephFSID uuid.UUID `json:"ceph_fsid,omitempty" yaml:"ceph_fsid,omitempty"` - Zones []*Zone -} - -// revive:enable:line-length-limit - -// ForEachMachine calls a function for each Machine in the cluster -// until instructed to terminate the loop -func (m *Zones) ForEachMachine(fn func(*Machine) bool) { - m.ForEachZone(func(z *Zone) bool { - var term bool - - z.ForEachMachine(func(p *Machine) bool { - term = fn(p) - return term - }) - - return term - }) -} - -// ForEachZone calls a function for each Zone in the cluster -// until instructed to terminate the loop -func (m *Zones) ForEachZone(fn func(*Zone) bool) { - for _, p := range m.Zones { - if fn(p) { - // terminate - return - } - } -} - -// GetMachineByName looks for a machine with the specified -// name on any zone -func (m *Zones) GetMachineByName(name string) (*Machine, bool) { - var out *Machine - - if name != "" { - m.ForEachMachine(func(p *Machine) bool { - if p.Name == name { - out = p - } - - return out != nil - }) - } - - return out, out != nil -}