mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-24 17:04:30 +00:00
Durability mode implementation (sync_all, sync_quorum, best_effort): - DurabilityMode type with superblock persistence, parse/validate/string - MakeDistributedSync mode-aware barrier enforcement in dist_group_commit - blockerr sentinel package (ErrDurabilityBarrierFailed, ErrDurabilityQuorumLost) - gRPC create path: mode validation, idempotent create consistency, partial cleanup - F1: strict mode rejects partial replica provisioning with cleanup - F3: empty heartbeat does not overwrite persisted strict mode - F4: SCSI error mapping uses errors.Is sentinels (not string matching) - Proto/wire/blockapi/CLI/UI plumbing for durability_mode field - Observability dashboard: cluster health cards + per-volume columns Testrunner platform (YAML-driven integration test framework): - Engine, parser, registry, reporter (JUnit XML + HTML), metrics scraping - 52 registered actions: block, iSCSI, I/O, fault injection, assertions - Baseline regression framework with 7 hard-fail conditions - 15 YAML scenarios (smoke, crash, HA, fault, consistency, snapshot) - 49 unit tests for testrunner internals QA adversarial suite (21 tests, all PASS): - Idempotent create mode/RF mismatch detection - Heartbeat mode downgrade prevention (F3) - sync_all/sync_quorum partial replica enforcement (F1) - Concurrent create race safety - Failover/expand mode preservation - Cleanup resilience when delete fails - Master restart auto-register mode handling - Superblock roundtrip all 3 modes - Validate edge cases (mode×RF matrix) - RequiredReplicas quorum math verification - Sentinel error categorization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package infra
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// LogCollector is implemented by any target that can provide a log file.
|
|
type LogCollector interface {
|
|
CollectLog() (string, error)
|
|
}
|
|
|
|
// ArtifactCollector gathers diagnostic info on failure.
|
|
type ArtifactCollector struct {
|
|
Dir string // base artifacts directory
|
|
Node *Node // initiator node for dmesg/lsblk
|
|
Log *log.Logger
|
|
}
|
|
|
|
// NewArtifactCollector creates a collector rooted at the given directory.
|
|
func NewArtifactCollector(dir string, node *Node, logger *log.Logger) *ArtifactCollector {
|
|
if logger == nil {
|
|
logger = log.Default()
|
|
}
|
|
return &ArtifactCollector{
|
|
Dir: dir,
|
|
Node: node,
|
|
Log: logger,
|
|
}
|
|
}
|
|
|
|
// Collect gathers diagnostics when failed is true.
|
|
func (a *ArtifactCollector) Collect(failed bool, tgt LogCollector, label string) {
|
|
if !failed {
|
|
return
|
|
}
|
|
a.CollectLabeled(tgt, label)
|
|
}
|
|
|
|
// CollectLabeled gathers diagnostics unconditionally.
|
|
func (a *ArtifactCollector) CollectLabeled(tgt LogCollector, label string) {
|
|
ts := time.Now().Format("20060102-150405")
|
|
testDir := filepath.Join(a.Dir, ts)
|
|
if err := os.MkdirAll(testDir, 0755); err != nil {
|
|
a.Log.Printf("artifacts: mkdir failed: %v", err)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
if tgt != nil {
|
|
if logContent, err := tgt.CollectLog(); err == nil && logContent != "" {
|
|
a.writeArtifact(filepath.Join(testDir, label+".log"), logContent)
|
|
}
|
|
}
|
|
|
|
if stdout, _, _, err := a.Node.RunRoot(ctx, "iscsiadm -m session 2>&1"); err == nil {
|
|
a.writeArtifact(filepath.Join(testDir, "iscsi-session.txt"), stdout)
|
|
}
|
|
|
|
if stdout, _, _, err := a.Node.RunRoot(ctx, "dmesg | tail -200"); err == nil {
|
|
a.writeArtifact(filepath.Join(testDir, "dmesg.txt"), stdout)
|
|
}
|
|
|
|
if stdout, _, _, err := a.Node.Run(ctx, "lsblk 2>&1"); err == nil {
|
|
a.writeArtifact(filepath.Join(testDir, "lsblk.txt"), stdout)
|
|
}
|
|
|
|
a.Log.Printf("artifacts saved to %s", testDir)
|
|
}
|
|
|
|
func (a *ArtifactCollector) writeArtifact(path, content string) {
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
a.Log.Printf("artifacts: write %s: %v", path, err)
|
|
} else {
|
|
a.Log.Printf("artifacts: wrote %s (%d bytes)", filepath.Base(path), len(content))
|
|
}
|
|
}
|
|
|
|
// CollectPerf saves performance results to a timestamped JSON file.
|
|
func (a *ArtifactCollector) CollectPerf(name string, data string) {
|
|
ts := time.Now().Format("20060102-150405")
|
|
path := filepath.Join(a.Dir, fmt.Sprintf("perf-%s-%s.json", name, ts))
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
a.Log.Printf("artifacts: mkdir failed: %v", err)
|
|
return
|
|
}
|
|
a.writeArtifact(path, data)
|
|
}
|