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 = /. // 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 }