mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-12 01:07:35 +02:00
Finding 1: Identity no longer address-derived - ReplicaAddr.ServerID field added (stable server identity from registry) - BlockVolumeAssignment.ReplicaServerID field added (scalar RF=2 path) - ControlBridge uses ServerID, NOT address, for ReplicaID - Missing ServerID → replica skipped (fail closed), logged Finding 2: Wired into real ProcessAssignments - BlockService.v2Bridge field initialized in StartBlockService - ProcessAssignments converts each assignment via v2Bridge.ConvertAssignment BEFORE existing V1 processing (parallel, not replacing yet) - Logged at glog V(1) Finding 3: Fail-closed on missing identity - Empty ServerID in ReplicaAddrs → replica skipped with log - Empty ReplicaServerID in scalar path → no replica created - Test: MissingServerID_FailsClosed verifies both paths 7 tests: StableServerID, AddressChange_IdentityPreserved, MultiReplica_StableServerIDs, MissingServerID_FailsClosed, EpochFencing_IntegratedPath, RebuildAssignment, ReplicaAssignment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
128 lines
4.7 KiB
Go
128 lines
4.7 KiB
Go
package blockvol
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
// BlockVolumeInfoMessage is the heartbeat status for one block volume.
|
|
// Mirrors the proto message that will be generated from master.proto.
|
|
type BlockVolumeInfoMessage struct {
|
|
Path string // volume file path (unique ID on this server)
|
|
VolumeSize uint64 // logical size in bytes
|
|
BlockSize uint32 // block size in bytes
|
|
Epoch uint64 // current fencing epoch
|
|
Role uint32 // blockvol.Role as uint32 for wire compat
|
|
WalHeadLsn uint64 // WAL head LSN
|
|
CheckpointLsn uint64 // last flushed LSN
|
|
HasLease bool // whether volume holds a valid lease
|
|
DiskType string // e.g., "ssd", "hdd"
|
|
ReplicaDataAddr string // receiver data listen addr (VS reports in heartbeat)
|
|
ReplicaCtrlAddr string // receiver ctrl listen addr
|
|
HealthScore float64 // CP8-2: 0.0-1.0
|
|
ScrubErrors int64 // CP8-2: lifetime scrub error count
|
|
LastScrubTime int64 // CP8-2: unix seconds
|
|
ReplicaDegraded bool // CP8-2: true if any replica shipper degraded
|
|
DurabilityMode string // CP8-3-1: "best_effort", "sync_all", "sync_quorum"
|
|
NvmeAddr string // NVMe/TCP target address (ip:port), empty if NVMe disabled
|
|
NQN string // NVMe subsystem NQN, empty if NVMe disabled
|
|
ReplicaShipperStates []ReplicaShipperStatus // CP13-7: per-replica state from primary's shipper group
|
|
}
|
|
|
|
// BlockVolumeShortInfoMessage is used for delta heartbeats
|
|
// (new/deleted block volumes).
|
|
type BlockVolumeShortInfoMessage struct {
|
|
Path string
|
|
VolumeSize uint64
|
|
BlockSize uint32
|
|
DiskType string
|
|
}
|
|
|
|
// BlockVolumeAssignment carries a role/epoch/lease assignment
|
|
// from master to volume server for one block volume.
|
|
type BlockVolumeAssignment struct {
|
|
Path string // which block volume
|
|
Epoch uint64 // new epoch
|
|
Role uint32 // target role (blockvol.Role as uint32)
|
|
LeaseTtlMs uint32 // lease TTL in milliseconds (0 = no lease)
|
|
ReplicaDataAddr string // where primary ships WAL data (scalar, RF=2 compat)
|
|
ReplicaCtrlAddr string // where primary sends barriers (scalar, RF=2 compat)
|
|
ReplicaServerID string // V2: stable server identity for scalar replica (from registry)
|
|
RebuildAddr string // where rebuild server listens
|
|
ReplicaAddrs []ReplicaAddr // CP8-2: multi-replica addrs (precedence over scalar)
|
|
}
|
|
|
|
// ToBlockVolumeInfoMessage converts a BlockVol's current state
|
|
// to a heartbeat info message. diskType is caller-supplied metadata
|
|
// (e.g. "ssd", "hdd") since the volume itself does not track disk type.
|
|
func ToBlockVolumeInfoMessage(path, diskType string, vol *BlockVol) BlockVolumeInfoMessage {
|
|
info := vol.Info()
|
|
status := vol.Status()
|
|
hs := vol.HealthStats()
|
|
return BlockVolumeInfoMessage{
|
|
Path: path,
|
|
VolumeSize: info.VolumeSize,
|
|
BlockSize: info.BlockSize,
|
|
Epoch: status.Epoch,
|
|
Role: RoleToWire(status.Role),
|
|
WalHeadLsn: status.WALHeadLSN,
|
|
CheckpointLsn: status.CheckpointLSN,
|
|
HasLease: status.HasLease,
|
|
DiskType: diskType,
|
|
HealthScore: status.HealthScore,
|
|
ScrubErrors: hs.ScrubErrors,
|
|
LastScrubTime: hs.LastScrubTime,
|
|
ReplicaDegraded: status.ReplicaDegraded,
|
|
DurabilityMode: vol.DurabilityMode().String(),
|
|
ReplicaShipperStates: vol.ReplicaShipperStates(),
|
|
}
|
|
}
|
|
|
|
// ToBlockVolumeShortInfoMessage returns a short info message
|
|
// for delta heartbeats. diskType is caller-supplied metadata.
|
|
func ToBlockVolumeShortInfoMessage(path, diskType string, vol *BlockVol) BlockVolumeShortInfoMessage {
|
|
info := vol.Info()
|
|
return BlockVolumeShortInfoMessage{
|
|
Path: path,
|
|
VolumeSize: info.VolumeSize,
|
|
BlockSize: info.BlockSize,
|
|
DiskType: diskType,
|
|
}
|
|
}
|
|
|
|
// maxValidRole is the highest defined Role value.
|
|
const maxValidRole = uint32(RoleDraining)
|
|
|
|
// RoleFromWire converts a uint32 wire role to blockvol.Role.
|
|
// Unknown values are mapped to RoleNone to prevent invalid roles
|
|
// from propagating through the system.
|
|
func RoleFromWire(r uint32) Role {
|
|
if r > maxValidRole {
|
|
return RoleNone
|
|
}
|
|
return Role(r)
|
|
}
|
|
|
|
// RoleToWire converts a blockvol.Role to uint32 for wire.
|
|
func RoleToWire(r Role) uint32 {
|
|
return uint32(r)
|
|
}
|
|
|
|
// LeaseTTLFromWire converts milliseconds to time.Duration.
|
|
func LeaseTTLFromWire(ms uint32) time.Duration {
|
|
return time.Duration(ms) * time.Millisecond
|
|
}
|
|
|
|
// LeaseTTLToWire converts time.Duration to milliseconds.
|
|
// Durations exceeding ~49.7 days are clamped to math.MaxUint32.
|
|
func LeaseTTLToWire(d time.Duration) uint32 {
|
|
ms := d.Milliseconds()
|
|
if ms > math.MaxUint32 {
|
|
return math.MaxUint32
|
|
}
|
|
if ms < 0 {
|
|
return 0
|
|
}
|
|
return uint32(ms)
|
|
}
|