Files
seaweedfs/sw-block/bridge/blockvol/storage_adapter.go
T
pingqiuandClaude Opus 4.6 8c326c871c feat: add contract interfaces and pin/release via release-func pattern (Phase 07 P1)
E5 handoff contract (contract.go):
- BlockVolReader: ReadState() → BlockVolState from real blockvol
- BlockVolPinner: HoldWALRetention/HoldSnapshot/HoldFullBase → release func
- BlockVolExecutor: StreamWALEntries/TransferSnapshot/TransferFullBase/TruncateWAL
- Clear import direction: weed-side imports sw-block, not reverse

StorageAdapter refactored:
- Consumes BlockVolReader + BlockVolPinner interfaces
- Pin/release uses release-func pattern (not map-based tracking)
- PushStorageAdapter for tests (push-based, no blockvol dependency)

10 bridge tests:
- 4 control adapter (identity, address change, role mapping, primary)
- 4 storage adapter (retained history, WAL pin reject, snapshot reject, symmetry)
- 1 E2E (assignment → adapter → engine → plan → execute → InSync)
- 1 contract interface verification

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

165 lines
4.9 KiB
Go

package blockvol
import (
"fmt"
"sync"
"sync/atomic"
engine "github.com/seaweedfs/seaweedfs/sw-block/engine/replication"
)
// StorageAdapter implements engine.StorageAdapter by consuming
// BlockVolReader and BlockVolPinner interfaces. When backed by
// real implementations from weed/storage/blockvol/v2bridge/,
// all fields come from actual blockvol state.
//
// For testing, use PushStorageAdapter (push-based, no blockvol dependency).
type StorageAdapter struct {
reader BlockVolReader
pinner BlockVolPinner
mu sync.Mutex
nextPinID atomic.Uint64
// Release functions keyed by pin ID.
releaseFuncs map[uint64]func()
}
// NewStorageAdapter creates a storage adapter backed by real blockvol
// reader and pinner interfaces.
func NewStorageAdapter(reader BlockVolReader, pinner BlockVolPinner) *StorageAdapter {
return &StorageAdapter{
reader: reader,
pinner: pinner,
releaseFuncs: map[uint64]func(){},
}
}
// GetRetainedHistory reads real blockvol state via BlockVolReader.
func (sa *StorageAdapter) GetRetainedHistory() engine.RetainedHistory {
state := sa.reader.ReadState()
return engine.RetainedHistory{
HeadLSN: state.WALHeadLSN,
TailLSN: state.WALTailLSN,
CommittedLSN: state.CommittedLSN,
CheckpointLSN: state.CheckpointLSN,
CheckpointTrusted: state.CheckpointTrusted,
}
}
// PinSnapshot delegates to BlockVolPinner.HoldSnapshot.
func (sa *StorageAdapter) PinSnapshot(checkpointLSN uint64) (engine.SnapshotPin, error) {
release, err := sa.pinner.HoldSnapshot(checkpointLSN)
if err != nil {
return engine.SnapshotPin{}, fmt.Errorf("snapshot pin at LSN %d: %w", checkpointLSN, err)
}
id := sa.nextPinID.Add(1)
sa.mu.Lock()
sa.releaseFuncs[id] = release
sa.mu.Unlock()
return engine.SnapshotPin{LSN: checkpointLSN, PinID: id, Valid: true}, nil
}
// ReleaseSnapshot calls the held release function.
func (sa *StorageAdapter) ReleaseSnapshot(pin engine.SnapshotPin) {
sa.mu.Lock()
release := sa.releaseFuncs[pin.PinID]
delete(sa.releaseFuncs, pin.PinID)
sa.mu.Unlock()
if release != nil {
release()
}
}
// PinWALRetention delegates to BlockVolPinner.HoldWALRetention.
func (sa *StorageAdapter) PinWALRetention(startLSN uint64) (engine.RetentionPin, error) {
release, err := sa.pinner.HoldWALRetention(startLSN)
if err != nil {
return engine.RetentionPin{}, fmt.Errorf("WAL retention pin at LSN %d: %w", startLSN, err)
}
id := sa.nextPinID.Add(1)
sa.mu.Lock()
sa.releaseFuncs[id] = release
sa.mu.Unlock()
return engine.RetentionPin{StartLSN: startLSN, PinID: id, Valid: true}, nil
}
// ReleaseWALRetention calls the held release function.
func (sa *StorageAdapter) ReleaseWALRetention(pin engine.RetentionPin) {
sa.mu.Lock()
release := sa.releaseFuncs[pin.PinID]
delete(sa.releaseFuncs, pin.PinID)
sa.mu.Unlock()
if release != nil {
release()
}
}
// PinFullBase delegates to BlockVolPinner.HoldFullBase.
func (sa *StorageAdapter) PinFullBase(committedLSN uint64) (engine.FullBasePin, error) {
release, err := sa.pinner.HoldFullBase(committedLSN)
if err != nil {
return engine.FullBasePin{}, fmt.Errorf("full base pin at LSN %d: %w", committedLSN, err)
}
id := sa.nextPinID.Add(1)
sa.mu.Lock()
sa.releaseFuncs[id] = release
sa.mu.Unlock()
return engine.FullBasePin{CommittedLSN: committedLSN, PinID: id, Valid: true}, nil
}
// ReleaseFullBase calls the held release function.
func (sa *StorageAdapter) ReleaseFullBase(pin engine.FullBasePin) {
sa.mu.Lock()
release := sa.releaseFuncs[pin.PinID]
delete(sa.releaseFuncs, pin.PinID)
sa.mu.Unlock()
if release != nil {
release()
}
}
// PushStorageAdapter is a test-only adapter that uses push-based state
// updates instead of pulling from a BlockVolReader. For use in tests
// that don't have real blockvol instances.
type PushStorageAdapter struct {
*StorageAdapter
state BlockVolState
}
// NewPushStorageAdapter creates a push-based adapter for tests.
func NewPushStorageAdapter() *PushStorageAdapter {
psa := &PushStorageAdapter{}
psa.StorageAdapter = NewStorageAdapter(&pushReader{psa: psa}, &pushPinner{psa: psa})
return psa
}
// UpdateState sets the adapter's state (push model for tests).
func (psa *PushStorageAdapter) UpdateState(state BlockVolState) {
psa.state = state
}
type pushReader struct{ psa *PushStorageAdapter }
func (pr *pushReader) ReadState() BlockVolState { return pr.psa.state }
type pushPinner struct{ psa *PushStorageAdapter }
func (pp *pushPinner) HoldWALRetention(startLSN uint64) (func(), error) {
if startLSN < pp.psa.state.WALTailLSN {
return nil, fmt.Errorf("WAL recycled past %d (tail=%d)", startLSN, pp.psa.state.WALTailLSN)
}
return func() {}, nil
}
func (pp *pushPinner) HoldSnapshot(checkpointLSN uint64) (func(), error) {
if !pp.psa.state.CheckpointTrusted || pp.psa.state.CheckpointLSN != checkpointLSN {
return nil, fmt.Errorf("no trusted checkpoint at %d", checkpointLSN)
}
return func() {}, nil
}
func (pp *pushPinner) HoldFullBase(_ uint64) (func(), error) {
return func() {}, nil
}