feat(M6): token-ring incremental log sync — append-only op log, delta rides token round-2, nodes converge on identical history (e2e verified join events)

This commit is contained in:
2026-08-18 08:59:59 +08:00
parent f6c4b91a96
commit e59feb82c1
10 changed files with 635 additions and 34 deletions

View File

@ -0,0 +1,47 @@
// Join helper: a newcomer POSTs its JoinInfo to a target node's join endpoint
// and adopts the returned ring state (leader preserved, newcomer inserted as
// the target's successor).
package cluster
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
// JoinRing asks target to add us to its ring and returns the adopted state.
func (e *Engine) JoinRing(ctx context.Context, targetAddr string, ji JoinInfo) error {
url := fmt.Sprintf("http://%s/api/manager/cluster/join", targetAddr)
body, err := json.Marshal(ji)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if e.User != "" || e.Pass != "" {
req.SetBasicAuth(e.User, e.Pass)
}
cli := &http.Client{Timeout: 8 * time.Second}
resp, err := cli.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("join %s -> HTTP %d", targetAddr, resp.StatusCode)
}
var out struct {
State State `json:"state"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return err
}
e.AdoptState(out.State)
return nil
}