diff --git a/pkg/zones/ceph_scan.go b/pkg/zones/ceph_scan.go new file mode 100644 index 0000000..32e3c7f --- /dev/null +++ b/pkg/zones/ceph_scan.go @@ -0,0 +1,173 @@ +package zones + +import ( + "net/netip" + "os" + "strings" + + "darvaza.org/core" + "git.jpi.io/amery/jpictl/pkg/ceph" +) + +// CephMissingMonitorError is an error that contains ceph +// monitors present in ceph.conf but not found on the cluster +type CephMissingMonitorError struct { + Names []string + Addrs []netip.Addr +} + +func (err *CephMissingMonitorError) appendName(name string) { + err.Names = append(err.Names, name) +} + +func (err *CephMissingMonitorError) appendAddr(addr netip.Addr) { + err.Addrs = append(err.Addrs, addr) +} + +// OK tells if this instance actual shouldn't be treated as an error +func (err CephMissingMonitorError) OK() bool { + switch { + case len(err.Names) > 0: + return false + case len(err.Addrs) > 0: + return false + default: + return true + } +} + +func (err CephMissingMonitorError) Error() string { + if !err.OK() { + var buf strings.Builder + + _, _ = buf.WriteString("missing:") + err.writeNames(&buf) + err.writeAddrs(&buf) + + return buf.String() + } + + // no error + return "" +} + +func (err *CephMissingMonitorError) writeNames(w *strings.Builder) { + if len(err.Names) > 0 { + _, _ = w.WriteString(" mon_host:") + for i, name := range err.Names { + if i != 0 { + _, _ = w.WriteRune(',') + } + _, _ = w.WriteString(name) + } + } +} + +func (err *CephMissingMonitorError) writeAddrs(w *strings.Builder) { + _, _ = w.WriteString(" mon_initial_members:") + for i, addr := range err.Addrs { + if i != 0 { + _, _ = w.WriteRune(',') + } + _, _ = w.WriteString(addr.String()) + } +} + +// AsError returns nil if the instance is actually OK +func (err *CephMissingMonitorError) AsError() error { + if err == nil || err.OK() { + return nil + } + + return err +} + +type cephScanTODO struct { + names map[string]bool + addrs map[string]bool +} + +func (todo *cephScanTODO) checkMachine(p *Machine) bool { + // on ceph all addresses are ring1 + ring1, _ := RingOneAddress(p.Zone(), p.ID) + addr := ring1.String() + + if _, found := todo.names[p.Name]; found { + // found on the TODO by name + todo.names[p.Name] = true + todo.addrs[addr] = true + return true + } + + if _, found := todo.addrs[addr]; found { + // found on the TODO by address + todo.names[p.Name] = true + todo.addrs[addr] = true + return true + } + + return false +} + +func (todo *cephScanTODO) Missing() error { + var check CephMissingMonitorError + + for name, found := range todo.names { + if !found { + check.appendName(name) + } + } + + for addr, found := range todo.addrs { + if !found { + var a netip.Addr + // it wouldn't be on the map if it wasn't valid + _ = a.UnmarshalText([]byte(addr)) + + check.appendAddr(a) + } + } + + return check.AsError() +} + +func newCephScanTODO(cfg *ceph.Config) *cephScanTODO { + todo := &cephScanTODO{ + names: make(map[string]bool), + addrs: make(map[string]bool), + } + + for _, name := range cfg.Global.Monitors { + todo.names[name] = false + } + + for _, addr := range cfg.Global.MonitorsAddr { + todo.addrs[addr.String()] = false + } + + return todo +} + +func (m *Zones) scanCephMonitors(_ *ScanOptions) error { + cfg, err := m.GetCephConfig() + switch { + case os.IsNotExist(err): + err = nil + case err != nil: + return err + } + + if cfg != nil { + // flag monitors based on config + todo := newCephScanTODO(cfg) + m.ForEachMachine(func(p *Machine) bool { + p.CephMonitor = todo.checkMachine(p) + return false + }) + if err := todo.Missing(); err != nil { + return core.Wrap(err, "ceph") + } + } + + return nil +} diff --git a/pkg/zones/scan.go b/pkg/zones/scan.go index 08734a3..d13879a 100644 --- a/pkg/zones/scan.go +++ b/pkg/zones/scan.go @@ -12,6 +12,7 @@ func (m *Zones) scan(opts *ScanOptions) error { m.scanZoneIDs, m.scanSort, m.scanGateways, + m.scanCephMonitors, } { if err := fn(opts); err != nil { return err