Files
seaweedfs/weed/storage/blockvol/v2bridge/command_bindings.go
T
pingqiuandClaude Opus 4.6 44103a1bd7 feat: Phase 20 acceptance fixes + sw-test-runner suite mode
Acceptance rows closed:
- WriteLBA/SyncCache contract: code comments document write-back vs
  durability fence semantics
- RF=2 stable identity: v2bridge always uses SetReplicaAddrs (preserves
  ServerID); blockcmd dispatcher also fixed to use setupPrimaryReplicationMulti;
  test asserts exact expected ReplicaID="vs-2" (not just non-empty)
- Tests treating WriteLBA as commit: replica_read_test rewritten with
  SyncCache as durability fence
- publish_healthy contract: 3 gate tests with hard assertions including
  gate 3 (PrimaryShipperConnected)
- SetReplicaAddr deprecation warning added
- WALShipper.ReplicaID() getter added for identity verification

Test runner enhancements:
- sw-test-runner suite command: build → deploy → run N scenarios in one
  invocation with --skip-deploy support
- Suite YAML definitions for T6 Stage 0 and Stage 1
- deploy action: kill stale processes, clean dirs, cross-compile, upload
- run-phase20-t6.ps1 PowerShell script (deprecated by suite command)

Engine/runtime fixes:
- Recovery executor nil-safety improvements
- Recovery bundle BuildRecoveryBundle defensive checks
- ShipperGroup MinReplicaFlushedLSNAll surface

Docs: acceptance checklist refined, test matrix updated, T6 runbook,
engine maintainer tutorial, design README updated.

26 files changed, ~1600 insertions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:30:54 -07:00

130 lines
3.5 KiB
Go

package v2bridge
import (
"fmt"
"hash/fnv"
"strings"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
)
type VolumeAccess interface {
WithVolume(path string, fn func(*blockvol.BlockVol) error) error
}
type ReceiverEndpoints struct {
DataAddr string
CtrlAddr string
}
// CommandBindings owns the concrete BlockVol-backed command operations.
// It performs backend binding only and does not own server-side event semantics.
type CommandBindings struct {
volumes VolumeAccess
listenAddr string
advertisedHost string
}
func NewCommandBindings(volumes VolumeAccess, listenAddr, advertisedHost string) *CommandBindings {
return &CommandBindings{
volumes: volumes,
listenAddr: listenAddr,
advertisedHost: advertisedHost,
}
}
func (b *CommandBindings) ApplyRole(a blockvol.BlockVolumeAssignment) error {
if b == nil || b.volumes == nil {
return nil
}
role := blockvol.RoleFromWire(a.Role)
ttl := blockvol.LeaseTTLFromWire(a.LeaseTtlMs)
return b.volumes.WithVolume(a.Path, func(vol *blockvol.BlockVol) error {
return vol.HandleAssignment(a.Epoch, role, ttl)
})
}
func (b *CommandBindings) StartReceiver(path, dataAddr, ctrlAddr string) (ReceiverEndpoints, error) {
if b == nil || b.volumes == nil {
return ReceiverEndpoints{}, nil
}
var endpoints ReceiverEndpoints
if err := b.volumes.WithVolume(path, func(vol *blockvol.BlockVol) error {
if b.advertisedHost != "" {
if err := vol.StartReplicaReceiver(dataAddr, ctrlAddr, b.advertisedHost); err != nil {
return err
}
} else {
if err := vol.StartReplicaReceiver(dataAddr, ctrlAddr); err != nil {
return err
}
}
if vol.ReplicaReceiverAddr() != nil {
endpoints.DataAddr = vol.ReplicaReceiverAddr().DataAddr
endpoints.CtrlAddr = vol.ReplicaReceiverAddr().CtrlAddr
}
return nil
}); err != nil {
return ReceiverEndpoints{}, err
}
if endpoints.DataAddr == "" {
endpoints.DataAddr = dataAddr
}
if endpoints.CtrlAddr == "" {
endpoints.CtrlAddr = ctrlAddr
}
return endpoints, nil
}
func (b *CommandBindings) ConfigurePrimaryReplication(path string, addrs []blockvol.ReplicaAddr) (string, error) {
if b == nil || b.volumes == nil || len(addrs) == 0 {
return "", nil
}
rebuildAddr := rebuildListenAddr(path, b.listenAddr)
if err := b.volumes.WithVolume(path, func(vol *blockvol.BlockVol) error {
// Always use SetReplicaAddrs to preserve ServerID on all paths,
// including RF=2 single-replica. SetReplicaAddr drops ServerID.
vol.SetReplicaAddrs(addrs)
if err := vol.StartRebuildServer(rebuildAddr); err != nil {
glog.Warningf("v2bridge: start rebuild server %s on %s: %v", path, rebuildAddr, err)
}
return nil
}); err != nil {
return "", err
}
return rebuildAddr, nil
}
func (b *CommandBindings) IsPrimaryShipperConnected(path string) bool {
if b == nil || b.volumes == nil {
return false
}
connected := false
_ = b.volumes.WithVolume(path, func(vol *blockvol.BlockVol) error {
connected = vol.PrimaryShipperConnected()
return nil
})
return connected
}
func rebuildListenAddr(path, listenAddr string) string {
basePort := 3260
if idx := strings.LastIndex(listenAddr, ":"); idx >= 0 {
var p int
if _, err := fmt.Sscanf(listenAddr[idx+1:], "%d", &p); err == nil && p > 0 {
basePort = p
}
}
h := fnv.New32a()
h.Write([]byte(path))
offset := int(h.Sum32()%500) * 3
dataPort := basePort + 1000 + offset
rebuildPort := dataPort + 2
host := listenAddr
if idx := strings.LastIndex(host, ":"); idx >= 0 {
host = host[:idx]
}
return fmt.Sprintf("%s:%d", host, rebuildPort)
}