package master import ( "context" "sync" "git.wzray.com/homelab/hivemind/internal/config" "git.wzray.com/homelab/hivemind/internal/roles" "git.wzray.com/homelab/hivemind/internal/state" "git.wzray.com/homelab/hivemind/internal/types" "git.wzray.com/homelab/hivemind/internal/web/client" ) type Role struct { state *state.RuntimeState config config.MasterConfig tasksGroup sync.WaitGroup observer *observer roles.BaseRole } func New(state *state.RuntimeState, config config.MasterConfig) *Role { return &Role{ state: state, config: config, observer: newObserver( state, config.ObserverInterval, config.BackoffSeconds, config.BackoffCount, ), } } func (r *Role) OnStartup(ctx context.Context) error { r.tasksGroup.Go(func() { r.observer.Start(ctx, func(n types.Node) error { _, err := r.onLeave(n) return err }) }) return nil } func (r *Role) OnShutdown() error { r.tasksGroup.Wait() return nil } func (r *Role) notify(path types.Path, v any) { for _, n := range r.state.Registry.Nodes() { addr := n.Endpoint r.tasksGroup.Go(func() { client.Post[any](addr, path, v) }) } } func (r *Role) onJoin(node types.Node) ([]types.Node, error) { if err := r.state.Registry.AddNode(node); err != nil { return nil, err } r.notify(types.PathNodeJoin, node) return r.state.Registry.AllNodes(), nil } func (r *Role) onLeave(node types.Node) (bool, error) { if err := r.state.Registry.RemoveNode(node.Hostname); err != nil { return false, err } r.notify(types.PathNodeLeave, node) return true, nil } func (r *Role) onKeepAlive(node types.Node) (bool, error) { if ok := r.state.Registry.Exists(node.Hostname); !ok { _, err := r.onJoin(node) return true, err } return false, nil } func (c *Role) RegisterHandlers(r types.Registrator) { r.Register(types.PostEndpoint(types.PathMasterJoin, c.onJoin)) r.Register(types.PostEndpoint(types.PathMasterLeave, c.onLeave)) r.Register(types.PostEndpoint(types.PathMasterKeepalive, c.onKeepAlive)) }