Files
seaweedfs/sw-block/bridge/blockvol/control_adapter.go
T
pingqiuandClaude Opus 4.6 11c6aaf316 feat: Batch 7 + Phase 16C-E — command dispatch extraction + engine refinements
Batch 7: Command dispatch binding extraction
- New weed/server/blockcmd package: CommandHandler interface + DispatchCommands
- volume_server_block.go applyCoreCommandsWithAssignment delegates to dispatcher
- weed/server still owns RecordCommand, EmitCoreEvent, PublishProjection
- v2bridge NOT given command-switch or event-emission semantics

Phase 16C: Rebuilding assignment enters core command path
Phase 16D: Rebuild recovery-task startup is command-driven
Phase 16E: Catch-up recovery-task startup is command-driven

Engine refinements:
- RecoveryTarget on AssignmentDelivered event
- shouldStartRecoveryTask / shouldStartReceiver guards
- bootstrapReason: awaiting_rebuild_start

Bridge/contract updates:
- control_adapter.go: refined translation helpers
- contract.go: executor port alignment

Migration design docs (Batch 1-3 delivered, design artifacts):
- v2-first/second/third-migration-batch.md + task-pack.md
- v2-assignment-translation-unification.md
- v2-execution-muscles-inventory.md
- v2-separation-port-layer-audit.md
- v2-legacy-runtime-exit-criteria.md

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

92 lines
3.1 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)
}
// ReplicaAssignmentForServer builds one engine replica assignment from stable
// volume/server identity plus endpoint. This is the canonical identity mapping
// shared by control ingestion and adapter-side assignment rebinding.
func ReplicaAssignmentForServer(volumeName, serverID string, endpoint engine.Endpoint) engine.ReplicaAssignment {
return engine.ReplicaAssignment{
ReplicaID: MakeReplicaID(volumeName, serverID),
Endpoint: endpoint,
}
}
// RecoveryTargetForRole maps a role-shaped control input to the bounded engine
// recovery target. This is a pure translation rule, not a recovery policy
// decision.
func RecoveryTargetForRole(role string) engine.SessionKind {
switch role {
case "replica":
return engine.SessionCatchUp
case "rebuilding":
return engine.SessionRebuild
default:
return ""
}
}
// 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 {
replica := ReplicaAssignmentForServer(r.VolumeName, r.ReplicaServerID, engine.Endpoint{
DataAddr: r.DataAddr,
CtrlAddr: r.CtrlAddr,
Version: r.AddrVersion,
})
intent.Replicas = append(intent.Replicas, replica)
// Map role to recovery intent (if needed).
kind := RecoveryTargetForRole(r.Role)
if kind != "" {
if intent.RecoveryTargets == nil {
intent.RecoveryTargets = map[string]engine.SessionKind{}
}
intent.RecoveryTargets[replica.ReplicaID] = kind
}
}
return intent
}