mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-20 06:07:05 +00:00
doc: testrunner roadmap + dm-stripe scenario (42/42 PASS, 1.87x write IOPS)
testrunner-roadmap.md: P0-P3 feature plan for multi-version comparison, Ceph adapter, result tracking, cluster templates, debug mode. dm-stripe-two-server.yaml: proven Linux dm-stripe across 2 sw-block volumes on 2 servers. Results: single=42K IOPS → striped=79K IOPS (1.87x). Data integrity verified via md5. Zero sw-block code changes needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
25ede892b4
commit
676539d3b9
@@ -0,0 +1,448 @@
|
||||
# sw-test-runner Roadmap
|
||||
|
||||
Date: 2026-04-11
|
||||
Status: active
|
||||
|
||||
## 1. Current State
|
||||
|
||||
The sw-test-runner is a YAML-driven test platform for sw-block hardware
|
||||
validation. It deploys binaries, orchestrates multi-node scenarios, runs
|
||||
benchmarks, injects faults, and reports results.
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Registered actions | 37 |
|
||||
| YAML scenarios (internal) | 72 |
|
||||
| YAML scenarios (external) | 75 |
|
||||
| Scenario categories | smoke, HA, rebuild, chaos, perf, soak, CSI |
|
||||
| Hardware tested | m01/m02, 25Gbps RoCE |
|
||||
|
||||
### What it does well
|
||||
|
||||
- Deploy weed binaries to remote nodes via SSH
|
||||
- Start/stop/kill weed processes (master, volume server)
|
||||
- Create/delete block volumes via master gRPC API
|
||||
- iSCSI and NVMe-oF connect/disconnect
|
||||
- fio benchmark with JSON output + metric parsing
|
||||
- dd write/read/verify with md5 checksum
|
||||
- Fault injection: netem delay, iptables partition, disk fill, WAL corrupt
|
||||
- Phase-based scenario sequencing with variable substitution
|
||||
- Assertions: equal, greater, contains, block field checks
|
||||
- Artifact collection on failure
|
||||
- JUnit XML output for CI
|
||||
- Parallel phase support
|
||||
- Prometheus metrics scraping
|
||||
|
||||
### What it cannot do yet
|
||||
|
||||
- Compare multiple binaries (V1/V2/V3) in one run
|
||||
- Compare against other systems (Ceph RBD, DRBD)
|
||||
- Track results over time (no persistent history)
|
||||
- Detect performance regressions automatically
|
||||
- Debug failures (auto-grep logs, stop-on-fail)
|
||||
- Deploy clusters from scratch (relies on pre-installed weed)
|
||||
- Reuse cluster setup across scenarios (boilerplate every time)
|
||||
|
||||
## 2. Proven Scenarios (V2 Baseline)
|
||||
|
||||
These 4 scenarios define the V2 acceptance bar. All PASS as of 2026-04-11.
|
||||
|
||||
| Scenario | File | Actions | What it proves |
|
||||
|----------|------|---------|----------------|
|
||||
| I-V3 Auto-Failover | `recovery-baseline-failover.yaml` | 43/43 | Create→write→kill→promote→IO verified |
|
||||
| I-R8 Rebuild-Rejoin | `v2-rebuild-rejoin.yaml` | 58/58 | Failover→write→restart→1GB rebuild→data verified |
|
||||
| Fast Rejoin | `v2-fast-rejoin-catchup.yaml` | 43/43 | Kill replica→3s restart→recovery→data verified |
|
||||
| RF=1 Perf Baseline | `rf1-perf-compare.yaml` | 22/22 | V1.5 vs V2 IOPS comparison (within 1.2%) |
|
||||
|
||||
Additional proven scenarios:
|
||||
|
||||
| Scenario | File | Actions | What it proves |
|
||||
|----------|------|---------|----------------|
|
||||
| Rebuild Retry | `v2-rebuild-failure-retry.yaml` | 52/52 | Kill during rebuild→restart→data verified |
|
||||
| dm-stripe 2-server | `dm-stripe-two-server.yaml` | 42/42 | Linux striping across 2 BlockVols: 1.87x write IOPS |
|
||||
|
||||
## 3. Roadmap
|
||||
|
||||
### P0: Multi-Version Comparison Matrix
|
||||
|
||||
**Goal**: Run the same benchmark scenario against V1.5, V2, V3, and Ceph
|
||||
in one invocation. Produce a side-by-side comparison table.
|
||||
|
||||
**Why P0**: CEO needs head-to-head performance numbers for investor/customer
|
||||
presentations. Current workflow requires manual binary swaps and separate runs.
|
||||
|
||||
**Design**:
|
||||
|
||||
```yaml
|
||||
# New top-level field in scenario YAML:
|
||||
matrix:
|
||||
binary:
|
||||
- name: v1.5
|
||||
weed: /opt/work/weed-v1.5
|
||||
- name: v2
|
||||
weed: /opt/work/weed
|
||||
- name: ceph
|
||||
type: ceph
|
||||
pool: bench-pool
|
||||
|
||||
# Runner executes the scenario once per matrix entry,
|
||||
# collects metrics, produces comparison table.
|
||||
```
|
||||
|
||||
**Output**:
|
||||
|
||||
```
|
||||
=== perf-compare: 4K randwrite qd=32, RF=1, 15s ===
|
||||
|
||||
| Binary | Write IOPS | Write P99 | Read IOPS | Read P99 |
|
||||
|--------|-----------|-----------|-----------|----------|
|
||||
| v1.5 | 47,233 | 1,234 us | 62,100 | 890 us |
|
||||
| v2 | 46,666 | 1,250 us | 61,800 | 910 us |
|
||||
| ceph | 4,533 | 14,483 us | 65,761 | 1,319 us |
|
||||
```
|
||||
|
||||
**New code**:
|
||||
|
||||
| Component | Description | Est. lines |
|
||||
|-----------|-------------|-----------|
|
||||
| Matrix parser | Parse `matrix:` from YAML, generate run combinations | 80 |
|
||||
| Matrix executor | Run scenario N times, collect results per variant | 120 |
|
||||
| Comparison reporter | Markdown/terminal table from collected metrics | 80 |
|
||||
| Total | | ~280 |
|
||||
|
||||
**Acceptance**: `sw-test-runner compare rf1-perf-compare.yaml` produces
|
||||
the table above with real numbers from hardware.
|
||||
|
||||
---
|
||||
|
||||
### P0: Ceph RBD Adapter
|
||||
|
||||
**Goal**: Run fio benchmarks against Ceph RBD on the same hardware, using
|
||||
the same scenario YAML. Enable head-to-head comparison.
|
||||
|
||||
**Why P0**: Validates the 13.5x write IOPS advantage claim. Without this,
|
||||
performance claims are from separate, non-reproducible runs.
|
||||
|
||||
**Prerequisites**: Ceph cluster deployed on m01/m02 (one monitor, two OSDs).
|
||||
This is a one-time manual setup.
|
||||
|
||||
**New actions**:
|
||||
|
||||
| Action | Parameters | What it does |
|
||||
|--------|-----------|--------------|
|
||||
| `ceph_create_image` | pool, name, size | `rbd create --size {size} {pool}/{name}` |
|
||||
| `ceph_map` | pool, name, node, save_as | `rbd map {pool}/{name}` → returns /dev/rbdN |
|
||||
| `ceph_unmap` | device, node | `rbd unmap {device}` |
|
||||
| `ceph_delete_image` | pool, name | `rbd rm {pool}/{name}` |
|
||||
|
||||
**Est. lines**: ~100 (4 actions, each ~25 lines wrapping CLI commands)
|
||||
|
||||
**Acceptance**: `ceph_create_image` + `ceph_map` + `fio_json` + `ceph_unmap`
|
||||
works in a scenario YAML.
|
||||
|
||||
---
|
||||
|
||||
### P1: Structured Results + Trend Tracking
|
||||
|
||||
**Goal**: Persist results in a queryable format. Detect performance
|
||||
regressions automatically.
|
||||
|
||||
**Why P1**: Without history, we can't tell if a code change degraded
|
||||
performance. Each run is ephemeral.
|
||||
|
||||
**Design**:
|
||||
|
||||
```
|
||||
results/
|
||||
index.json ← append-only run index
|
||||
runs/
|
||||
20260411-140236/
|
||||
meta.json ← scenario, binary, topology, timestamp
|
||||
metrics.json ← {iops_write: 46666, iops_read: 61800, ...}
|
||||
phases.json ← per-phase timing and pass/fail
|
||||
artifacts/ ← logs, core dumps on failure
|
||||
baselines/
|
||||
rf1-perf.json ← rolling baseline: mean, stddev, threshold
|
||||
```
|
||||
|
||||
**New capabilities**:
|
||||
|
||||
| Feature | Description | Est. lines |
|
||||
|---------|-------------|-----------|
|
||||
| `metrics.json` writer | Extract metrics from fio/dd results, write structured JSON | 80 |
|
||||
| `index.json` updater | Append each run to index with scenario/binary/timestamp | 40 |
|
||||
| Baseline comparator | Compare latest metrics against baseline, flag regressions | 100 |
|
||||
| `sw-test-runner trend` | CLI command to show metric trend over last N runs | 80 |
|
||||
| Total | | ~300 |
|
||||
|
||||
**Regression detection rule**:
|
||||
|
||||
```
|
||||
if latest.iops < baseline.mean - 2 * baseline.stddev:
|
||||
WARN: performance regression detected
|
||||
latest: 38,000 IOPS
|
||||
baseline: 46,666 ± 1,200 IOPS (last 10 runs)
|
||||
```
|
||||
|
||||
**Acceptance**: After 5 runs of `rf1-perf-compare.yaml`, `sw-test-runner trend`
|
||||
shows IOPS over time and would flag a >5% regression.
|
||||
|
||||
---
|
||||
|
||||
### P1: Cluster Templates
|
||||
|
||||
**Goal**: Remove the 20-line cluster boilerplate from every scenario.
|
||||
Define reusable topology+cluster configs.
|
||||
|
||||
**Why P1**: Every new scenario copies the same `cluster-start` phase.
|
||||
Changes to cluster config (ports, dirs) require editing every scenario.
|
||||
|
||||
**Design**:
|
||||
|
||||
```yaml
|
||||
# templates/two-node-roce.yaml
|
||||
cluster:
|
||||
master:
|
||||
node: m02
|
||||
port: 9433
|
||||
dir: /tmp/sw-master
|
||||
extra_args: "-ip=10.0.0.3"
|
||||
volumes:
|
||||
- node: m02
|
||||
port: 18480
|
||||
dir: /tmp/sw-vs1
|
||||
block_dir: /tmp/sw-vs1/blocks
|
||||
block_listen: ":3295"
|
||||
extra_args: "-ip=10.0.0.3"
|
||||
- node: m01
|
||||
port: 18480
|
||||
dir: /tmp/sw-vs2
|
||||
block_dir: /tmp/sw-vs2/blocks
|
||||
block_listen: ":3295"
|
||||
extra_args: "-ip=10.0.0.1"
|
||||
```
|
||||
|
||||
```yaml
|
||||
# scenario references template:
|
||||
name: rebuild-rejoin
|
||||
cluster: two-node-roce # ← replaces 20 lines of cluster-start
|
||||
phases:
|
||||
- name: create-and-write
|
||||
...
|
||||
```
|
||||
|
||||
**New code**:
|
||||
|
||||
| Component | Description | Est. lines |
|
||||
|-----------|-------------|-----------|
|
||||
| Template loader | Parse cluster template YAML | 60 |
|
||||
| Auto cluster-start phase | Generate start actions from template | 80 |
|
||||
| Auto cleanup phase | Generate stop actions from template | 40 |
|
||||
| Variable injection | Expose `{{ master_pid }}`, `{{ vs1_pid }}`, etc. | 30 |
|
||||
| Total | | ~210 |
|
||||
|
||||
**Acceptance**: `v2-rebuild-rejoin.yaml` works with `cluster: two-node-roce`
|
||||
instead of inline cluster-start phase.
|
||||
|
||||
---
|
||||
|
||||
### P2: Debug Mode
|
||||
|
||||
**Goal**: When a scenario fails, automatically collect diagnostic
|
||||
information and optionally pause for manual investigation.
|
||||
|
||||
**Why P2**: This session's debugging workflow was: scenario fails → SSH
|
||||
to node → grep logs → find root cause → fix → redeploy → rerun. The
|
||||
runner should automate the grep+collect step.
|
||||
|
||||
**New CLI flags**:
|
||||
|
||||
```bash
|
||||
sw-test-runner run scenario.yaml --debug # auto-grep + stop on fail
|
||||
sw-test-runner run scenario.yaml --stop-on-fail # pause, don't cleanup
|
||||
sw-test-runner run scenario.yaml --grep "pattern" # grep all node logs
|
||||
```
|
||||
|
||||
**Auto-diagnostic on failure**:
|
||||
|
||||
```
|
||||
=== FAILURE DIAGNOSTIC ===
|
||||
Phase: restart-old-primary
|
||||
Action: wait_volume_healthy (timeout 60s)
|
||||
|
||||
--- m01 volume.log (last 20 lines matching "error|fail|warn|panic") ---
|
||||
W0409 15:41:18 recovery: rebuild execution failed: sender not found
|
||||
...
|
||||
|
||||
--- m02 volume.log (last 20 lines matching "error|fail|warn|panic") ---
|
||||
(clean)
|
||||
|
||||
--- m02 master.log (last 10 lines matching "failover|promote|assign") ---
|
||||
I0409 15:41:12 failover: promoted replica for "v2-rebuild"
|
||||
...
|
||||
```
|
||||
|
||||
**New code**:
|
||||
|
||||
| Component | Description | Est. lines |
|
||||
|-----------|-------------|-----------|
|
||||
| `--stop-on-fail` handler | Skip cleanup, print SSH commands | 30 |
|
||||
| Auto-grep on failure | SSH to all nodes, grep key patterns | 80 |
|
||||
| Log merge by timestamp | Merge multi-node logs, sort by time | 60 |
|
||||
| Diagnostic formatter | Terminal-friendly failure report | 40 |
|
||||
| Total | | ~210 |
|
||||
|
||||
**Acceptance**: When `v2-rebuild-rejoin.yaml` fails, the runner
|
||||
automatically shows the "sender not found" error from the logs
|
||||
without manual SSH.
|
||||
|
||||
---
|
||||
|
||||
### P2: RF=2 Performance Benchmark Suite
|
||||
|
||||
**Goal**: Standardized benchmark scenario that matches the V1 bench
|
||||
parameters exactly. Produces results comparable to the CEO's numbers.
|
||||
|
||||
**Why P2**: Our current V2 perf numbers are RF=1 only. The CEO's
|
||||
benchmark was RF=2 sync_all over NVMe/TCP. We need the same config.
|
||||
|
||||
**Scenario**:
|
||||
|
||||
```yaml
|
||||
name: rf2-perf-benchmark
|
||||
cluster: two-node-roce
|
||||
|
||||
phases:
|
||||
- name: create-volume
|
||||
actions:
|
||||
- action: create_block_volume
|
||||
name: perf-vol
|
||||
size_bytes: "1073741824"
|
||||
replica_factor: "2"
|
||||
durability_mode: "sync_all"
|
||||
|
||||
- name: benchmark
|
||||
actions:
|
||||
# Warmup
|
||||
- action: fio_json
|
||||
rw: randwrite
|
||||
bs: 4k
|
||||
iodepth: "32"
|
||||
runtime: "10"
|
||||
|
||||
# 4K random write
|
||||
- action: fio_json
|
||||
rw: randwrite
|
||||
bs: 4k
|
||||
iodepth: "32"
|
||||
runtime: "15"
|
||||
save_as: write_result
|
||||
|
||||
# 4K random read
|
||||
- action: fio_json
|
||||
rw: randread
|
||||
bs: 4k
|
||||
iodepth: "32"
|
||||
runtime: "15"
|
||||
save_as: read_result
|
||||
|
||||
# 128K sequential write
|
||||
- action: fio_json
|
||||
rw: write
|
||||
bs: 128k
|
||||
iodepth: "16"
|
||||
runtime: "15"
|
||||
save_as: seq_write_result
|
||||
|
||||
# Report
|
||||
- action: print
|
||||
msg: |
|
||||
RF=2 sync_all Performance:
|
||||
4K randwrite: {{ write_iops }} IOPS, P99={{ write_p99 }}us
|
||||
4K randread: {{ read_iops }} IOPS, P99={{ read_p99 }}us
|
||||
128K seqwrite: {{ seq_bw }} MB/s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### P3: Full Cluster Deployment
|
||||
|
||||
**Goal**: Deploy a complete sw-block cluster (master + N volume servers +
|
||||
CSI driver) from scratch on bare metal or K8s.
|
||||
|
||||
**Why P3**: Currently assumes pre-installed binaries and manual OS setup.
|
||||
For CI/CD and customer POC, need push-button deployment.
|
||||
|
||||
**Scope**:
|
||||
|
||||
| Component | What it does |
|
||||
|-----------|-------------|
|
||||
| OS prereqs | Install iscsiadm, nvme-cli, fio, dmsetup |
|
||||
| Binary deployment | Build + SCP weed binary to all nodes |
|
||||
| Cluster bootstrap | Start master, wait ready, start volume servers |
|
||||
| Volume provisioning | Create volumes, configure replication |
|
||||
| Client setup | iSCSI/NVMe-oF discovery + login |
|
||||
| Health check | Verify all components healthy |
|
||||
|
||||
**Effort**: ~500 lines. Depends on target environment (bare metal vs K8s).
|
||||
|
||||
---
|
||||
|
||||
### P3: pgbench / Application Benchmark
|
||||
|
||||
**Goal**: Run real database benchmarks (pgbench TPC-B, sysbench OLTP)
|
||||
on sw-block volumes to produce application-level metrics.
|
||||
|
||||
**Why P3**: fio shows raw block performance. Customers care about
|
||||
"how fast is PostgreSQL on your storage."
|
||||
|
||||
**New actions**:
|
||||
|
||||
| Action | What it does |
|
||||
|--------|-------------|
|
||||
| `pgbench_init` | `pgbench -i -s {scale} -h {host} {db}` |
|
||||
| `pgbench_run` | `pgbench -c {clients} -T {time} -h {host} {db}` → parse TPS |
|
||||
| `start_postgres` | Start PostgreSQL on a block PV |
|
||||
| `stop_postgres` | Stop PostgreSQL cleanly |
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementation Priority
|
||||
|
||||
| Priority | Feature | Lines | Impact |
|
||||
|----------|---------|-------|--------|
|
||||
| **P0** | Multi-version comparison matrix | 280 | CEO benchmark table |
|
||||
| **P0** | Ceph RBD adapter | 100 | Head-to-head validation |
|
||||
| **P1** | Structured results + trends | 300 | Regression detection |
|
||||
| **P1** | Cluster templates | 210 | Scenario authoring speed |
|
||||
| **P2** | Debug mode | 210 | Debugging speed |
|
||||
| **P2** | RF=2 perf benchmark suite | 1 scenario | V1 number comparison |
|
||||
| **P3** | Full cluster deployment | 500 | CI/CD, customer POC |
|
||||
| **P3** | pgbench / app benchmarks | 200 | Customer-facing metrics |
|
||||
| **Total** | | ~1,800 | |
|
||||
|
||||
## 5. Versioning Strategy
|
||||
|
||||
The testrunner should work across sw-block versions:
|
||||
|
||||
| Version | Binary | Scenarios | Expected behavior |
|
||||
|---------|--------|-----------|-------------------|
|
||||
| V1.5 | `weed-v1.5` | `rf1-perf-compare` only | RF=1 perf baseline |
|
||||
| V2 | `weed` (current) | All 147 scenarios | Full coverage |
|
||||
| V3 | `weed-v3` (future) | Same 4 acceptance scenarios | Must match V2 results |
|
||||
| Ceph | `rbd` CLI | Perf scenarios via adapter | Comparison baseline |
|
||||
|
||||
The matrix runner handles this — same scenario, different binary, automatic
|
||||
comparison.
|
||||
|
||||
## 6. Non-Goals
|
||||
|
||||
The testrunner should NOT become:
|
||||
|
||||
- A full CI/CD pipeline (use GitHub Actions / Jenkins for that)
|
||||
- A monitoring system (use Prometheus/Grafana for ongoing monitoring)
|
||||
- A configuration management tool (use Ansible/Terraform for cluster setup)
|
||||
- A log aggregation system (use ELK/Loki for production log analysis)
|
||||
|
||||
It IS: a focused hardware validation and benchmark tool for sw-block
|
||||
development and customer demos.
|
||||
@@ -0,0 +1,285 @@
|
||||
name: dm-stripe-two-server
|
||||
timeout: 5m
|
||||
|
||||
# Test: dm-stripe across 2 BlockVols on 2 servers.
|
||||
# Proves Linux-layer striping works with sw-block for high throughput.
|
||||
#
|
||||
# Topology:
|
||||
# m01: client + VS2 (BlockVol B)
|
||||
# m02: master + VS1 (BlockVol A)
|
||||
# Client connects iSCSI to both, dm-stripe combines them.
|
||||
|
||||
env:
|
||||
master_url: "http://10.0.0.3:9433"
|
||||
vol_size: "1073741824"
|
||||
|
||||
topology:
|
||||
nodes:
|
||||
m01:
|
||||
host: 192.168.1.181
|
||||
alt_ips: ["10.0.0.1"]
|
||||
user: testdev
|
||||
key: "/opt/work/testdev_key"
|
||||
m02:
|
||||
host: 192.168.1.184
|
||||
alt_ips: ["10.0.0.3"]
|
||||
user: testdev
|
||||
key: "/opt/work/testdev_key"
|
||||
|
||||
phases:
|
||||
- name: cluster-start
|
||||
actions:
|
||||
- action: exec
|
||||
node: m02
|
||||
cmd: "fuser -k 9433/tcp 18480/tcp 2>/dev/null; sleep 1; rm -rf /tmp/sw-stripe-master /tmp/sw-stripe-vs1 && mkdir -p /tmp/sw-stripe-master /tmp/sw-stripe-vs1/blocks"
|
||||
root: "true"
|
||||
ignore_error: true
|
||||
- action: exec
|
||||
node: m01
|
||||
cmd: "fuser -k 18480/tcp 2>/dev/null; sleep 1; rm -rf /tmp/sw-stripe-vs2 && mkdir -p /tmp/sw-stripe-vs2/blocks"
|
||||
root: "true"
|
||||
ignore_error: true
|
||||
|
||||
- action: start_weed_master
|
||||
node: m02
|
||||
port: "9433"
|
||||
dir: /tmp/sw-stripe-master
|
||||
extra_args: "-ip=10.0.0.3"
|
||||
save_as: master_pid
|
||||
|
||||
- action: sleep
|
||||
duration: 3s
|
||||
|
||||
- action: start_weed_volume
|
||||
node: m02
|
||||
port: "18480"
|
||||
master: "10.0.0.3:9433"
|
||||
dir: /tmp/sw-stripe-vs1
|
||||
extra_args: "-block.dir=/tmp/sw-stripe-vs1/blocks -block.listen=:3295 -ip=10.0.0.3"
|
||||
save_as: vs1_pid
|
||||
|
||||
- action: start_weed_volume
|
||||
node: m01
|
||||
port: "18480"
|
||||
master: "10.0.0.3:9433"
|
||||
dir: /tmp/sw-stripe-vs2
|
||||
extra_args: "-block.dir=/tmp/sw-stripe-vs2/blocks -block.listen=:3295 -ip=10.0.0.1"
|
||||
save_as: vs2_pid
|
||||
|
||||
- action: sleep
|
||||
duration: 3s
|
||||
|
||||
- action: wait_cluster_ready
|
||||
node: m02
|
||||
master_url: "{{ master_url }}"
|
||||
|
||||
- action: wait_block_servers
|
||||
count: "2"
|
||||
|
||||
- name: create-volumes
|
||||
actions:
|
||||
# Create 2 RF=1 volumes — one will land on each server
|
||||
- action: create_block_volume
|
||||
name: stripe-a
|
||||
size_bytes: "{{ vol_size }}"
|
||||
replica_factor: "1"
|
||||
|
||||
- action: create_block_volume
|
||||
name: stripe-b
|
||||
size_bytes: "{{ vol_size }}"
|
||||
replica_factor: "1"
|
||||
|
||||
- action: sleep
|
||||
duration: 5s
|
||||
|
||||
- name: discover-placement
|
||||
actions:
|
||||
# Find where each volume was placed
|
||||
- action: discover_primary
|
||||
name: stripe-a
|
||||
save_as: pri_a
|
||||
|
||||
- action: discover_primary
|
||||
name: stripe-b
|
||||
save_as: pri_b
|
||||
|
||||
- action: print
|
||||
msg: "stripe-a on {{ pri_a }} ({{ pri_a_server }}), stripe-b on {{ pri_b }} ({{ pri_b_server }})"
|
||||
|
||||
- name: connect-iscsi
|
||||
actions:
|
||||
# Connect to both volumes from m01 using discovered addresses
|
||||
- action: lookup_block_volume
|
||||
name: stripe-a
|
||||
save_as: vol_a
|
||||
|
||||
- action: lookup_block_volume
|
||||
name: stripe-b
|
||||
save_as: vol_b
|
||||
|
||||
- action: iscsi_login_direct
|
||||
node: m01
|
||||
host: "{{ vol_a_iscsi_host }}"
|
||||
port: "{{ vol_a_iscsi_port }}"
|
||||
iqn: "{{ vol_a_iqn }}"
|
||||
save_as: dev_a
|
||||
|
||||
- action: iscsi_login_direct
|
||||
node: m01
|
||||
host: "{{ vol_b_iscsi_host }}"
|
||||
port: "{{ vol_b_iscsi_port }}"
|
||||
iqn: "{{ vol_b_iqn }}"
|
||||
save_as: dev_b
|
||||
|
||||
- action: print
|
||||
msg: "Connected: dev_a={{ dev_a }} (m02), dev_b={{ dev_b }} (m01)"
|
||||
|
||||
- name: setup-stripe
|
||||
actions:
|
||||
# Create dm-stripe: 64KB stripe unit across both devices
|
||||
# stripe_sectors = 64KB / 512B = 128 sectors
|
||||
# total_sectors = size of one device (they must be equal)
|
||||
- action: exec
|
||||
node: m01
|
||||
cmd: |
|
||||
SECTORS_A=$(blockdev --getsz {{ dev_a }})
|
||||
SECTORS_B=$(blockdev --getsz {{ dev_b }})
|
||||
TOTAL=$((SECTORS_A + SECTORS_B))
|
||||
echo "Device A: {{ dev_a }} = ${SECTORS_A} sectors"
|
||||
echo "Device B: {{ dev_b }} = ${SECTORS_B} sectors"
|
||||
echo "Total: ${TOTAL} sectors"
|
||||
echo "0 ${TOTAL} striped 2 128 {{ dev_a }} 0 {{ dev_b }} 0" | dmsetup create sw-stripe0
|
||||
ls -la /dev/mapper/sw-stripe0
|
||||
root: "true"
|
||||
save_as: stripe_setup
|
||||
|
||||
- action: print
|
||||
msg: "dm-stripe created: /dev/mapper/sw-stripe0 ({{ stripe_setup }})"
|
||||
|
||||
- name: benchmark-single
|
||||
actions:
|
||||
# Baseline: single volume performance (dev_a only)
|
||||
- action: fio_json
|
||||
node: m01
|
||||
device: "{{ dev_a }}"
|
||||
rw: randwrite
|
||||
bs: 4k
|
||||
iodepth: "32"
|
||||
runtime: "10"
|
||||
time_based: "true"
|
||||
name: single_vol_write
|
||||
save_as: single_vol_write
|
||||
|
||||
- action: fio_parse
|
||||
json_var: single_vol_write
|
||||
metric: iops
|
||||
save_as: single_write_iops
|
||||
|
||||
- action: fio_json
|
||||
node: m01
|
||||
device: "{{ dev_a }}"
|
||||
rw: randread
|
||||
bs: 4k
|
||||
iodepth: "32"
|
||||
runtime: "10"
|
||||
time_based: "true"
|
||||
name: single_vol_read
|
||||
save_as: single_vol_read
|
||||
|
||||
- action: fio_parse
|
||||
json_var: single_vol_read
|
||||
metric: iops
|
||||
save_as: single_read_iops
|
||||
|
||||
- action: print
|
||||
msg: "SINGLE VOLUME: write={{ single_write_iops }} IOPS, read={{ single_read_iops }} IOPS"
|
||||
|
||||
- name: benchmark-stripe
|
||||
actions:
|
||||
# Striped: both volumes combined
|
||||
- action: fio_json
|
||||
node: m01
|
||||
device: /dev/mapper/sw-stripe0
|
||||
rw: randwrite
|
||||
bs: 4k
|
||||
iodepth: "32"
|
||||
runtime: "10"
|
||||
time_based: "true"
|
||||
name: stripe_write
|
||||
save_as: stripe_write
|
||||
|
||||
- action: fio_parse
|
||||
json_var: stripe_write
|
||||
metric: iops
|
||||
save_as: stripe_write_iops
|
||||
|
||||
- action: fio_json
|
||||
node: m01
|
||||
device: /dev/mapper/sw-stripe0
|
||||
rw: randread
|
||||
bs: 4k
|
||||
iodepth: "32"
|
||||
runtime: "10"
|
||||
time_based: "true"
|
||||
name: stripe_read
|
||||
save_as: stripe_read
|
||||
|
||||
- action: fio_parse
|
||||
json_var: stripe_read
|
||||
metric: iops
|
||||
save_as: stripe_read_iops
|
||||
|
||||
- action: print
|
||||
msg: "STRIPED (2x): write={{ stripe_write_iops }} IOPS, read={{ stripe_read_iops }} IOPS"
|
||||
|
||||
- action: print
|
||||
msg: "COMPARISON: single_write={{ single_write_iops }} → stripe_write={{ stripe_write_iops }} | single_read={{ single_read_iops }} → stripe_read={{ stripe_read_iops }}"
|
||||
|
||||
- name: verify-data
|
||||
actions:
|
||||
# Write known data to stripe, read back, verify
|
||||
- action: dd_write
|
||||
node: m01
|
||||
device: /dev/mapper/sw-stripe0
|
||||
bs: 1M
|
||||
count: "10"
|
||||
sync_mode: fsync
|
||||
save_as: stripe_md5
|
||||
|
||||
- action: dd_read_md5
|
||||
node: m01
|
||||
device: /dev/mapper/sw-stripe0
|
||||
bs: 1M
|
||||
count: "10"
|
||||
save_as: stripe_verify
|
||||
|
||||
- action: assert_equal
|
||||
actual: "{{ stripe_verify }}"
|
||||
expected: "{{ stripe_md5 }}"
|
||||
|
||||
- action: print
|
||||
msg: "STRIPE DATA INTEGRITY VERIFIED: md5={{ stripe_md5 }}"
|
||||
|
||||
- name: cleanup
|
||||
always: true
|
||||
actions:
|
||||
- action: exec
|
||||
node: m01
|
||||
cmd: "dmsetup remove sw-stripe0 2>/dev/null; true"
|
||||
root: "true"
|
||||
ignore_error: true
|
||||
- action: iscsi_cleanup
|
||||
node: m01
|
||||
ignore_error: true
|
||||
- action: stop_weed
|
||||
node: m01
|
||||
pid: "{{ vs2_pid }}"
|
||||
ignore_error: true
|
||||
- action: stop_weed
|
||||
node: m02
|
||||
pid: "{{ vs1_pid }}"
|
||||
ignore_error: true
|
||||
- action: stop_weed
|
||||
node: m02
|
||||
pid: "{{ master_pid }}"
|
||||
ignore_error: true
|
||||
Reference in New Issue
Block a user