package node import ( "context" "errors" "fmt" "sync" "time" "git.wzray.com/homelab/hivemind/internal/app" "git.wzray.com/homelab/hivemind/internal/config" "git.wzray.com/homelab/hivemind/internal/transport" "git.wzray.com/homelab/hivemind/internal/transport/node" "git.wzray.com/homelab/hivemind/internal/types" "github.com/rs/zerolog/log" ) type Role struct { state *app.State keepaliveGroup sync.WaitGroup config config.NodeConfig } func New(state *app.State, config config.NodeConfig) *Role { return &Role{ state: state, config: config, } } func (r *Role) OnStartup(ctx context.Context) error { r.keepaliveGroup.Go(r.keepaliveFunc(ctx)) return nil } func (r *Role) OnShutdown() error { r.keepaliveGroup.Wait() return nil } func (n *Role) RegisterHandlers(r transport.Registrator) { node.Register(r, n) } func (r *Role) Join(bootstrap string) error { masters := make(map[string]struct{}) for _, node := range r.state.Registry.ByRole(types.MasterRole) { if node.Hostname == r.state.Self.Hostname { continue } masters[node.Endpoint] = struct{}{} } if bootstrap != "" { masters[bootstrap] = struct{}{} } else if len(masters) == 0 { return errors.New("no masters configured") } var errs []error for m := range masters { logger := log.With().Str("host", m).Logger() logger.Debug().Msg("trying to join via master") nodes, err := r.state.Clients.Master.Join(m, r.state.Self) if err != nil { errs = append(errs, err) logger.Debug().Err(err).Msg("unable to join") continue } if err := r.state.Registry.Set(nodes); err != nil { logger.Debug().Err(err).Msg("unable to set master's nodes") errs = append(errs, err) continue } return nil } return fmt.Errorf("unable to join with any master: %w", errors.Join(errs...)) } func (r *Role) Leave() error { masters := r.state.Registry.ByRole(types.MasterRole) if len(masters) == 0 { return nil } var errs []error for _, m := range masters { logger := log.With().Str("name", m.Hostname).Logger() logger.Debug().Msg("sending leave message") if err := r.state.Clients.Master.Leave(m.Endpoint, r.state.Self); err != nil { logger.Debug().Err(err).Msg("unable to send leave message") errs = append(errs, err) continue } else { logger.Debug().Msg("leave message sent") return nil } } return fmt.Errorf("unable to send leave message to any master: %w", errors.Join(errs...)) } func (r *Role) Healthcheck() (string, error) { return "OK", nil } func (r *Role) keepaliveFunc(ctx context.Context) func() { sendKeepalive := func() { for _, m := range r.state.Registry.ByRole(types.MasterRole) { logger := log.With().Str("name", m.Hostname).Logger() logger.Debug().Msg("sending keepalive packet") nodes, err := r.state.Clients.Master.Heartbeat(m.Endpoint, r.state.Self) if err != nil { logger.Info().Err(err).Msg("unable to send keepalive packet") continue } logger.Debug().Msg("keepalive packet sent") if err := r.state.Registry.Set(nodes); err != nil { logger.Warn().Err(err).Msg("unable to set masters nodes") continue } break } } return func() { for { select { case <-ctx.Done(): return case <-time.After(time.Duration(r.config.KeepaliveInterval) * time.Second): sendKeepalive() } } } }