Files
seaweedfs/sw-block/runtime/volumev2/continuity_runtime.go
T
pingqiuandClaude Opus 4.6 044a6d770b feat: Phase 19 — bounded working RF2 block path
Live HTTP evidence transport, continuous Loop2 service, bounded auto
failover trigger, runtime-managed frontend export, bounded replica
repair, end-to-end RF2 handoff with continued I/O on new primary,
bounded operator HTTP surface, and CSI V2 runtime backend adapter.

11 new proof tests covering the full M6-M10 chain plus CSI create/
lookup/publish through the V2 runtime path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:12:00 -07:00

147 lines
4.4 KiB
Go

package volumev2
import (
"bytes"
"fmt"
"slices"
)
// ReplicatedContinuityResult captures one bounded replicated continuity run
// through the current runtime-owned path.
type ReplicatedContinuityResult struct {
VolumeName string
SourcePrimaryNodeID string
SelectedPrimaryNodeID string
ExpectedEpoch uint64
Loop2BeforeFailover Loop2RuntimeSnapshot
Failover FailoverResult
ReadBackLength uint32
DataMatch bool
}
// ReplicatedContinuitySnapshot is the read-only observable result of the most
// recent bounded continuity run for one volume.
type ReplicatedContinuitySnapshot struct {
VolumeName string
LastError string
Result ReplicatedContinuityResult
}
// ExecuteReplicatedContinuity runs one bounded continuity statement through the
// current runtime:
// mirror writes to the bounded participant set -> observe active Loop 2 ->
// fail over to the survivor set -> read back data from the newly selected
// primary. This is a bounded continuity closure, not a full replication product
// claim.
func (m *InProcessRuntimeManager) ExecuteReplicatedContinuity(volumeName, sourcePrimaryNodeID string, expectedEpoch uint64, survivorNodeIDs []string, lba uint64, payload []byte) (result ReplicatedContinuityResult, runErr error) {
if m == nil {
return ReplicatedContinuityResult{}, fmt.Errorf("volumev2: runtime manager is nil")
}
if volumeName == "" {
return ReplicatedContinuityResult{}, fmt.Errorf("volumev2: continuity volume name is required")
}
if sourcePrimaryNodeID == "" {
return ReplicatedContinuityResult{}, fmt.Errorf("volumev2: continuity source primary node id is required")
}
if len(payload) == 0 {
return ReplicatedContinuityResult{}, fmt.Errorf("volumev2: continuity payload is required")
}
if len(survivorNodeIDs) == 0 {
return ReplicatedContinuityResult{}, fmt.Errorf("volumev2: continuity survivor node ids are required")
}
result = ReplicatedContinuityResult{
VolumeName: volumeName,
SourcePrimaryNodeID: sourcePrimaryNodeID,
ExpectedEpoch: expectedEpoch,
}
defer func() {
snapshot := ReplicatedContinuitySnapshot{
VolumeName: volumeName,
Result: result,
}
if runErr != nil {
snapshot.LastError = runErr.Error()
}
m.recordContinuitySnapshot(volumeName, snapshot)
}()
nodeIDs := append([]string{sourcePrimaryNodeID}, survivorNodeIDs...)
nodeIDs = uniqueSorted(nodeIDs)
nodes := make([]*Node, 0, len(nodeIDs))
for _, nodeID := range nodeIDs {
node, err := m.localNode(nodeID)
if err != nil {
runErr = err
return result, runErr
}
nodes = append(nodes, node)
}
for _, node := range nodes {
if err := node.WriteLBA(volumeName, lba, payload); err != nil {
runErr = fmt.Errorf("volumev2: continuity write %s on %s: %w", volumeName, node.NodeID(), err)
return result, runErr
}
}
for _, node := range nodes {
if err := node.SyncCache(volumeName); err != nil {
runErr = fmt.Errorf("volumev2: continuity sync %s on %s: %w", volumeName, node.NodeID(), err)
return result, runErr
}
}
loop2Snap, err := m.ObserveLoop2(volumeName, sourcePrimaryNodeID, expectedEpoch, nodeIDs...)
if err != nil {
runErr = err
return result, runErr
}
result.Loop2BeforeFailover = loop2Snap
failover, err := m.ExecuteFailover(volumeName, expectedEpoch, survivorNodeIDs...)
result.Failover = failover
if err != nil {
runErr = err
return result, runErr
}
result.SelectedPrimaryNodeID = failover.Assignment.NodeID
selectedNode, err := m.localNode(failover.Assignment.NodeID)
if err != nil {
runErr = err
return result, runErr
}
readBack, err := selectedNode.ReadLBA(volumeName, lba, uint32(len(payload)))
if err != nil {
runErr = fmt.Errorf("volumev2: continuity readback %s on %s: %w", volumeName, selectedNode.NodeID(), err)
return result, runErr
}
result.ReadBackLength = uint32(len(readBack))
result.DataMatch = bytes.Equal(readBack, payload)
if !result.DataMatch {
runErr = fmt.Errorf("volumev2: continuity payload mismatch after failover")
return result, runErr
}
return result, nil
}
func uniqueSorted(values []string) []string {
if len(values) == 0 {
return nil
}
seen := make(map[string]struct{}, len(values))
out := make([]string, 0, len(values))
for _, value := range values {
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
out = append(out, value)
}
slices.Sort(out)
return out
}