feat: swarm membership discovery via peer-list gossip
Refs #1 - /v1/ping now advertises the node's known peers (peer_id, addr, name) - Run sweeps static KNOX_PEERS + persisted discovered peers, enqueueing newly-learned addresses for direct sweeps (membership-only relay; no observation relay) - db: ShareablePeers, SwarmPeerAddrs, MergePeer (cursor-preserving discovery upsert), MaxHCLForNode - integration test: a node configured with a single seed discovers and pulls from other swarm members without direct configuration
This commit is contained in:
+49
-12
@@ -31,10 +31,11 @@ type Node struct {
|
||||
|
||||
// pingResponse is the anti-entropy summary returned by /v1/ping.
|
||||
type pingResponse struct {
|
||||
NodeID string `json:"node_id"`
|
||||
Name string `json:"name"`
|
||||
Vector map[string]int64 `json:"vector"` // node_id → max hcl
|
||||
MaxHCL *int64 `json:"max_hcl,omitempty"`
|
||||
NodeID string `json:"node_id"`
|
||||
Name string `json:"name"`
|
||||
Vector map[string]int64 `json:"vector"` // node_id → max hcl
|
||||
Peers []db.PeerInfo `json:"peers"` // swarm membership this node knows
|
||||
MaxHCL *int64 `json:"max_hcl,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Node) Handler() http.Handler {
|
||||
@@ -52,10 +53,16 @@ func (n *Node) handlePing(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
peers, err := n.Kdb.ShareablePeers()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, pingResponse{
|
||||
NodeID: n.Kdb.NodeID(),
|
||||
Name: n.Name,
|
||||
Vector: vector,
|
||||
Peers: peers,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -255,17 +262,32 @@ func (c *Client) Diff() (*DiffSummary, error) {
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// Run executes one anti-entropy sweep against the given peer addresses.
|
||||
func Run(kdb *db.KnoxDB, peers []string) {
|
||||
// Run executes one anti-entropy + membership sweep.
|
||||
//
|
||||
// Peers are a fusion of the statically configured list (KNOX_PEERS) and peers
|
||||
// previously discovered and persisted in the peers table (swarm join): syncing
|
||||
// with any one node reveals who else is in the swarm, and those nodes are then
|
||||
// swept too. There is no relay of observations — only membership is shared; each
|
||||
// node pulls/pushes directly with every other node it learns about.
|
||||
func Run(kdb *db.KnoxDB, static []string) {
|
||||
myID := kdb.NodeID()
|
||||
for _, addr := range peers {
|
||||
addr = strings.TrimSpace(addr)
|
||||
if addr == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(addr, kdb.NodeID()+":") {
|
||||
|
||||
// Seed the work queue with static config plus persisted discoveries.
|
||||
persisted, _ := kdb.SwarmPeerAddrs()
|
||||
work := make([]string, 0, len(static)+len(persisted))
|
||||
work = append(work, static...)
|
||||
work = append(work, persisted...)
|
||||
|
||||
seen := make(map[string]bool) // addr → handled (also suppresses self)
|
||||
queue := 0
|
||||
for queue < len(work) {
|
||||
addr := strings.TrimSpace(work[queue])
|
||||
queue++
|
||||
if addr == "" || seen[addr] {
|
||||
continue
|
||||
}
|
||||
seen[addr] = true
|
||||
|
||||
c := &Client{Addr: addr, Timeout: 10 * time.Second}
|
||||
p, err := c.Ping()
|
||||
if err != nil {
|
||||
@@ -276,6 +298,21 @@ func Run(kdb *db.KnoxDB, peers []string) {
|
||||
continue // never talk to ourselves (or an aliased address)
|
||||
}
|
||||
|
||||
// Membership discovery: learn who else is in the swarm and enqueue
|
||||
// their addresses for direct sweeps.
|
||||
for _, pi := range p.Peers {
|
||||
if pi.PeerID == "" || pi.PeerID == myID || pi.Addr == "" {
|
||||
continue
|
||||
}
|
||||
if err := kdb.MergePeer(pi.PeerID, pi.Addr, pi.Name); err != nil {
|
||||
log.Printf("[gossip] merge peer %s: %v", pi.PeerID, err)
|
||||
continue
|
||||
}
|
||||
if !seen[pi.Addr] {
|
||||
work = append(work, pi.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
pulled := 0
|
||||
for remoteNode, remoteHCL := range p.Vector {
|
||||
localHCL := kdbVectorGet(kdb, remoteNode)
|
||||
|
||||
Reference in New Issue
Block a user