mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-19 14:34:15 +00:00
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>
84 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|