mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-26 18:04:33 +00:00
M1 milestone: failover evidence crosses transport/session seam. Adapter seam (failover_adapter.go): - FailoverEvidenceAdapter: query-side (promotion evidence + replica summary) - FailoverTakeoverAdapter: execution-side (prepare + gate) - FailoverTarget: binds NodeID + both adapters - NewInProcessFailoverTarget: factory for in-process case Transport seam (failover_evidence_transport.go): - FailoverEvidenceTransport: request/response interface with nodeID routing - FailoverEvidenceHandler: server-side registration - InMemoryFailoverEvidenceTransport: first transport impl (in-memory) - NewHybridInProcessFailoverTarget: transport-backed evidence + local takeover Runtime manager (runtime_manager.go): - InProcessRuntimeManager: participant registry + ExecuteFailover entry point - Persisted failover snapshots/results per-volume and global-last All failover paths (session/driver/manager) now go through adapter seam. Old FailoverParticipant preserved as compatibility wrapper only. Phase 18 docs: phase-18.md (M1-M5 structure), log, decisions. Design docs updated: kernel-closure-review, claim-and-evidence ledger. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
136 lines
3.9 KiB
Go
136 lines
3.9 KiB
Go
package volumev2
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"sync"
|
|
|
|
"github.com/seaweedfs/seaweedfs/sw-block/runtime/masterv2"
|
|
)
|
|
|
|
// InProcessFailoverDriver is the first thin driver that wires one in-process
|
|
// masterv2 instance to a set of failover-capable participants. It owns no
|
|
// recovery logic; it only resolves participants and constructs sessions.
|
|
type InProcessFailoverDriver struct {
|
|
master *masterv2.Master
|
|
|
|
mu sync.RWMutex
|
|
targets map[string]FailoverTarget
|
|
}
|
|
|
|
// NewInProcessFailoverDriver creates a driver for one in-process masterv2.
|
|
func NewInProcessFailoverDriver(master *masterv2.Master) (*InProcessFailoverDriver, error) {
|
|
if master == nil {
|
|
return nil, fmt.Errorf("volumev2: master is nil")
|
|
}
|
|
return &InProcessFailoverDriver{
|
|
master: master,
|
|
targets: make(map[string]FailoverTarget),
|
|
}, nil
|
|
}
|
|
|
|
// RegisterTarget binds one stable node id to one explicit failover target.
|
|
func (d *InProcessFailoverDriver) RegisterTarget(target FailoverTarget) error {
|
|
if d == nil {
|
|
return fmt.Errorf("volumev2: failover driver is nil")
|
|
}
|
|
if target.NodeID == "" {
|
|
return fmt.Errorf("volumev2: target node id is required")
|
|
}
|
|
if target.Evidence == nil {
|
|
return fmt.Errorf("volumev2: target %q missing evidence adapter", target.NodeID)
|
|
}
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
d.targets[target.NodeID] = target
|
|
return nil
|
|
}
|
|
|
|
// RegisterParticipant preserves the old convenience path by wrapping one
|
|
// legacy failover participant into an explicit target.
|
|
func (d *InProcessFailoverDriver) RegisterParticipant(nodeID string, participant FailoverParticipant) error {
|
|
if participant == nil {
|
|
return fmt.Errorf("volumev2: participant %q is nil", nodeID)
|
|
}
|
|
return d.RegisterTarget(FailoverTarget{
|
|
NodeID: nodeID,
|
|
Evidence: participant,
|
|
Takeover: participant,
|
|
})
|
|
}
|
|
|
|
// UnregisterParticipant removes one node from the in-process driver.
|
|
func (d *InProcessFailoverDriver) UnregisterParticipant(nodeID string) {
|
|
if d == nil || nodeID == "" {
|
|
return
|
|
}
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
delete(d.targets, nodeID)
|
|
}
|
|
|
|
// ParticipantNodeIDs returns the currently registered node ids in stable order.
|
|
func (d *InProcessFailoverDriver) ParticipantNodeIDs() []string {
|
|
if d == nil {
|
|
return nil
|
|
}
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
nodeIDs := make([]string, 0, len(d.targets))
|
|
for nodeID := range d.targets {
|
|
nodeIDs = append(nodeIDs, nodeID)
|
|
}
|
|
slices.Sort(nodeIDs)
|
|
return nodeIDs
|
|
}
|
|
|
|
// NewSession constructs a FailoverSession using either the requested node ids
|
|
// or all currently registered participants when none are specified.
|
|
func (d *InProcessFailoverDriver) NewSession(volumeName string, expectedEpoch uint64, nodeIDs ...string) (*FailoverSession, error) {
|
|
if d == nil {
|
|
return nil, fmt.Errorf("volumev2: failover driver is nil")
|
|
}
|
|
targets, err := d.resolveTargets(nodeIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewFailoverSession(d.master, volumeName, expectedEpoch, targets)
|
|
}
|
|
|
|
// Execute runs one failover using the resolved participant set.
|
|
func (d *InProcessFailoverDriver) Execute(volumeName string, expectedEpoch uint64, nodeIDs ...string) (FailoverResult, error) {
|
|
session, err := d.NewSession(volumeName, expectedEpoch, nodeIDs...)
|
|
if err != nil {
|
|
return FailoverResult{}, err
|
|
}
|
|
return session.Run()
|
|
}
|
|
|
|
func (d *InProcessFailoverDriver) resolveTargets(nodeIDs []string) ([]FailoverTarget, error) {
|
|
d.mu.RLock()
|
|
defer d.mu.RUnlock()
|
|
|
|
if len(d.targets) == 0 {
|
|
return nil, fmt.Errorf("volumev2: no failover targets registered")
|
|
}
|
|
|
|
resolvedIDs := nodeIDs
|
|
if len(resolvedIDs) == 0 {
|
|
resolvedIDs = make([]string, 0, len(d.targets))
|
|
for nodeID := range d.targets {
|
|
resolvedIDs = append(resolvedIDs, nodeID)
|
|
}
|
|
slices.Sort(resolvedIDs)
|
|
}
|
|
|
|
targets := make([]FailoverTarget, 0, len(resolvedIDs))
|
|
for _, nodeID := range resolvedIDs {
|
|
target, ok := d.targets[nodeID]
|
|
if !ok {
|
|
return nil, fmt.Errorf("volumev2: unknown failover target %q", nodeID)
|
|
}
|
|
targets = append(targets, target)
|
|
}
|
|
return targets, nil
|
|
}
|