Files
seaweedfs/sw-block/bridge/blockvol/control_adapter.go
T
pingqiuandClaude Opus 4.6 05daede7f9 feat: add V2 bridge adapters for blockvol (Phase 07 P0)
Creates sw-block/bridge/blockvol/ — concrete adapters connecting
the V2 engine to real blockvol storage and control-plane state.

control_adapter.go:
- MakeReplicaID: volume-name/server-id (NOT address-derived)
- ToAssignmentIntent: maps master assignment → engine intent
- Role → SessionKind translation (pure mapping, no policy)

storage_adapter.go:
- BlockVolState: maps to real blockvol fields (WAL head/tail,
  committed, checkpoint) — NOT reconstructed from metadata
- GetRetainedHistory from real state
- PinSnapshot rejects untrusted checkpoint
- PinWALRetention rejects recycled range
- PinFullBase / ReleaseFullBase

8 bridge tests:
- StableIdentity: ReplicaID = vol/server (not address)
- AddressChangePreservesIdentity: same ID, different address
- RebuildRoleMapping: "rebuilding" → SessionRebuild
- PrimaryNoRecovery: no recovery targets for primary
- RetainedHistoryFromRealState: all fields from BlockVolState
- WALPinRejectsRecycled: tail validation
- SnapshotPinRejectsInvalid: trust validation
- E2E_AssignmentToRecovery: master assignment → adapter →
  engine intent → plan → execute → InSync

Adapter replacement order:
P0: control_adapter + storage_adapter (this delivery)
P1: executor_bridge + observe_adapter (deferred)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 17:39:39 -07:00

84 lines
2.7 KiB
Go

package blockvol
import (
"fmt"
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
)
// MasterAssignment represents a block-volume assignment from the master,
// as delivered via heartbeat response. This is the raw input from the
// existing master_grpc_server / block_heartbeat_loop path.
type MasterAssignment struct {
VolumeName string // e.g., "pvc-data-1"
Epoch uint64
Role string // "primary", "replica", "rebuilding"
PrimaryServerID string // which server is the primary
ReplicaServerID string // which server is this replica
DataAddr string // replica's current data address
CtrlAddr string // replica's current control address
AddrVersion uint64 // bumped on address change
}
// ControlAdapter converts master assignments into engine AssignmentIntent.
// Identity mapping: ReplicaID = <volume-name>/<replica-server-id>.
// This adapter does NOT decide recovery policy — it only translates
// master role/state into engine SessionKind.
type ControlAdapter struct{}
// NewControlAdapter creates a control adapter.
func NewControlAdapter() *ControlAdapter {
return &ControlAdapter{}
}
// MakeReplicaID derives a stable engine ReplicaID from volume + server identity.
// NOT derived from any address field.
func MakeReplicaID(volumeName, serverID string) string {
return fmt.Sprintf("%s/%s", volumeName, serverID)
}
// ToAssignmentIntent converts a master assignment into an engine intent.
// The adapter maps role transitions to SessionKind but does NOT decide
// the actual recovery outcome (that's the engine's job).
func (ca *ControlAdapter) ToAssignmentIntent(primary MasterAssignment, replicas []MasterAssignment) engine.AssignmentIntent {
intent := engine.AssignmentIntent{
Epoch: primary.Epoch,
}
for _, r := range replicas {
replicaID := MakeReplicaID(r.VolumeName, r.ReplicaServerID)
intent.Replicas = append(intent.Replicas, engine.ReplicaAssignment{
ReplicaID: replicaID,
Endpoint: engine.Endpoint{
DataAddr: r.DataAddr,
CtrlAddr: r.CtrlAddr,
Version: r.AddrVersion,
},
})
// Map role to recovery intent (if needed).
kind := mapRoleToSessionKind(r.Role)
if kind != "" {
if intent.RecoveryTargets == nil {
intent.RecoveryTargets = map[string]engine.SessionKind{}
}
intent.RecoveryTargets[replicaID] = kind
}
}
return intent
}
// mapRoleToSessionKind maps a master-assigned role to an engine SessionKind.
// This is a pure translation — NO policy decision.
func mapRoleToSessionKind(role string) engine.SessionKind {
switch role {
case "replica":
return engine.SessionCatchUp // default recovery for reconnecting replicas
case "rebuilding":
return engine.SessionRebuild
default:
return "" // no recovery needed (primary, or unknown)
}
}