mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-22 07:06:51 +00:00
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>
130 lines
3.1 KiB
Go
130 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRunSmoke_ProducesSingleNodeMVPResult(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "single-node-mvp.blk")
|
|
|
|
var out bytes.Buffer
|
|
oldOutput := commandOutput
|
|
commandOutput = &out
|
|
defer func() {
|
|
commandOutput = oldOutput
|
|
}()
|
|
|
|
runErr := runSmoke([]string{
|
|
"--path", path,
|
|
"--name", "mvp-vol",
|
|
"--node", "node-a",
|
|
"--write-text", "hello-v2",
|
|
})
|
|
if runErr != nil {
|
|
t.Fatalf("runSmoke: %v", runErr)
|
|
}
|
|
|
|
var result smokeResult
|
|
if err := json.Unmarshal(out.Bytes(), &result); err != nil {
|
|
t.Fatalf("unmarshal output %q: %v", out.String(), err)
|
|
}
|
|
if result.VolumeName != "mvp-vol" {
|
|
t.Fatalf("volume_name=%q", result.VolumeName)
|
|
}
|
|
if result.Role != "primary" {
|
|
t.Fatalf("role=%q", result.Role)
|
|
}
|
|
if result.Mode != "allocated_only" {
|
|
t.Fatalf("mode=%q", result.Mode)
|
|
}
|
|
if !strings.Contains(result.Readback, "68656c6c6f2d7632") {
|
|
t.Fatalf("readback_hex=%q", result.Readback)
|
|
}
|
|
}
|
|
|
|
func TestRunRestartSmoke_PreservesDataAcrossRestart(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "single-node-restart.blk")
|
|
|
|
var out bytes.Buffer
|
|
oldOutput := commandOutput
|
|
commandOutput = &out
|
|
defer func() {
|
|
commandOutput = oldOutput
|
|
}()
|
|
|
|
runErr := runRestartSmoke([]string{
|
|
"--path", path,
|
|
"--name", "restart-vol",
|
|
"--node", "node-a",
|
|
"--write-text", "hello-restart",
|
|
})
|
|
if runErr != nil {
|
|
t.Fatalf("runRestartSmoke: %v", runErr)
|
|
}
|
|
|
|
var result restartSmokeResult
|
|
if err := json.Unmarshal(out.Bytes(), &result); err != nil {
|
|
t.Fatalf("unmarshal output %q: %v", out.String(), err)
|
|
}
|
|
if result.VolumeName != "restart-vol" {
|
|
t.Fatalf("volume_name=%q", result.VolumeName)
|
|
}
|
|
if result.Role != "primary" {
|
|
t.Fatalf("role=%q", result.Role)
|
|
}
|
|
if result.Mode != "allocated_only" {
|
|
t.Fatalf("mode=%q", result.Mode)
|
|
}
|
|
if result.InitialWrite != result.PostRestart {
|
|
t.Fatalf("initial=%q post_restart=%q", result.InitialWrite, result.PostRestart)
|
|
}
|
|
if !strings.Contains(result.PostRestart, "68656c6c6f2d72657374617274") {
|
|
t.Fatalf("post_restart_hex=%q", result.PostRestart)
|
|
}
|
|
}
|
|
|
|
func TestRunISCSISmoke_ExportsFrontendAndReportsAddress(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "single-node-iscsi.blk")
|
|
|
|
var out bytes.Buffer
|
|
oldOutput := commandOutput
|
|
commandOutput = &out
|
|
defer func() {
|
|
commandOutput = oldOutput
|
|
}()
|
|
|
|
runErr := runISCSISmoke([]string{
|
|
"--path", path,
|
|
"--name", "iscsi-vol",
|
|
"--node", "node-a",
|
|
"--iqn", "iqn.2026-04.com.seaweedfs:test.cli",
|
|
})
|
|
if runErr != nil {
|
|
t.Fatalf("runISCSISmoke: %v", runErr)
|
|
}
|
|
|
|
var result iscsiSmokeResult
|
|
if err := json.Unmarshal(out.Bytes(), &result); err != nil {
|
|
t.Fatalf("unmarshal output %q: %v", out.String(), err)
|
|
}
|
|
if result.VolumeName != "iscsi-vol" {
|
|
t.Fatalf("volume_name=%q", result.VolumeName)
|
|
}
|
|
if result.Role != "primary" {
|
|
t.Fatalf("role=%q", result.Role)
|
|
}
|
|
if result.Mode != "allocated_only" {
|
|
t.Fatalf("mode=%q", result.Mode)
|
|
}
|
|
if result.IQN != "iqn.2026-04.com.seaweedfs:test.cli" {
|
|
t.Fatalf("iqn=%q", result.IQN)
|
|
}
|
|
if !strings.Contains(result.Address, "127.0.0.1:") {
|
|
t.Fatalf("address=%q", result.Address)
|
|
}
|
|
}
|