Files
seaweedfs/sw-block/cmd/purev2rf1/main.go
T
pingqiuandClaude Opus 4.6 b8c6944e3f feat: V2 MVP milestone — masterv2 + volumev2 + in-process failover
V2 runtime packages:
- sw-block/runtime/masterv2: identity authority (desired state,
  heartbeat handling, promotion arbitration via SelectPromotionCandidate)
- sw-block/runtime/volumev2: per-volume micro-cluster shell (node,
  orchestrator, control session, iSCSI frontend, takeover gate,
  failover session + driver, replica summary reconstruction)
- sw-block/runtime/purev2: RF1 execution shell (engine + store +
  dispatcher + local boundary observations)
- sw-block/runtime/protocolv2: three-channel separation
  (heartbeat/assignment/query + replica summary)

V2 binaries:
- sw-block/cmd/v2singleblock: single-node RF1 block server
- sw-block/cmd/purev2rf1: minimal RF1 runtime binary

Milestone capabilities:
- RF1 write/read/sync with engine-driven mode projection
- masterv2 ↔ volumev2 heartbeat convergence + assignment reissue
- Promotion query with fresh CommittedLSN/WALHeadLSN evidence
- Replica summary for bounded takeover reconstruction
- Primary-loss reconstruction from peer summaries (fail-closed gate)
- In-process failover driver with session observability
- Local boundary observations feed engine (Committed/Durable/Checkpoint)

Design docs:
- v2-two-loop-protocol.md: identity vs data-control separation
- v2-automata-ownership-map.md: event/command ownership split
- v2-loop1-surface-draft.md: heartbeat/query/assignment field spec
- v2-volumev2-single-node-mvp.md: target layering
- v2-kernel-closure-review.md: per-volume micro-cluster principle
- v2-pure-runtime-rf1-bootstrap.md, v2-capability-map.md,
  v2-proof-and-retest-pyramid.md

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

111 lines
2.6 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"time"
"github.com/seaweedfs/seaweedfs/sw-block/runtime/purev2"
"github.com/seaweedfs/seaweedfs/weed/storage/blockvol"
)
func main() {
if len(os.Args) < 2 {
usage()
os.Exit(2)
}
switch os.Args[1] {
case "bootstrap":
if err := runBootstrap(os.Args[2:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
case "status":
if err := runStatus(os.Args[2:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
default:
usage()
os.Exit(2)
}
}
func runBootstrap(args []string) error {
fs := flag.NewFlagSet("bootstrap", flag.ContinueOnError)
path := fs.String("path", "", "block volume path")
sizeBytes := fs.Uint64("size-bytes", 1*1024*1024, "logical volume size in bytes")
blockSize := fs.Uint("block-size", 4096, "block size in bytes")
walSize := fs.Uint64("wal-size", 256*1024, "wal size in bytes")
epoch := fs.Uint64("epoch", 1, "assignment epoch")
leaseMs := fs.Int("lease-ms", 30000, "lease ttl in milliseconds")
if err := fs.Parse(args); err != nil {
return err
}
if *path == "" {
return fmt.Errorf("bootstrap: --path is required")
}
rt := purev2.New(purev2.Config{})
defer rt.Close()
opts := blockvol.CreateOptions{
VolumeSize: *sizeBytes,
BlockSize: uint32(*blockSize),
WALSize: *walSize,
}
if err := rt.BootstrapPrimary(*path, opts, *epoch, time.Duration(*leaseMs)*time.Millisecond); err != nil {
return err
}
snap, err := rt.Snapshot(*path)
if err != nil {
return err
}
return printJSON(snap)
}
func runStatus(args []string) error {
fs := flag.NewFlagSet("status", flag.ContinueOnError)
path := fs.String("path", "", "block volume path")
epoch := fs.Uint64("epoch", 0, "optional epoch to rebuild core projection")
leaseMs := fs.Int("lease-ms", 30000, "lease ttl in milliseconds")
if err := fs.Parse(args); err != nil {
return err
}
if *path == "" {
return fmt.Errorf("status: --path is required")
}
rt := purev2.New(purev2.Config{})
defer rt.Close()
if err := rt.OpenVolume(*path); err != nil {
return err
}
if *epoch > 0 {
if err := rt.ApplyPrimaryAssignment(*path, *epoch, time.Duration(*leaseMs)*time.Millisecond); err != nil {
return err
}
}
snap, err := rt.Snapshot(*path)
if err != nil {
return err
}
return printJSON(snap)
}
func printJSON(v any) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(v)
}
func usage() {
fmt.Fprintln(os.Stderr, "usage:")
fmt.Fprintln(os.Stderr, " purev2rf1 bootstrap --path <file> [--size-bytes N --block-size N --wal-size N --epoch N]")
fmt.Fprintln(os.Stderr, " purev2rf1 status --path <file> [--epoch N]")
}