Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,16 @@ virtwork ships nine built-in workloads, grouped by purpose. With `--vm-count 1`

| Workload | VMs | Description | Tools |
|----------|-----|-------------|-------|
| **cpu** | N | Continuous CPU stress | `stress-ng --cpu 0 --cpu-method all` |
| **memory** | N | Memory pressure at 80% | `stress-ng --vm 1 --vm-bytes 80%` |
| **disk** | N | Mixed random and sequential I/O on a data disk | `fio` with multiple profiles |
| **database** | N | PostgreSQL with pgbench loop on a data disk | `pgbench -c 10 -j 2 -T 300` |
| **cpu** | N | Continuous CPU stress | `stress-ng --cpu 0 --cpu-load 100 --cpu-method all` (configurable via `params`) |
| **memory** | N | Memory pressure | `stress-ng --vm 1 --vm-bytes 80% --vm-method all` (configurable via `params`) |
| **disk** | N | Mixed random and sequential I/O on a data disk | `fio` with multiple profiles (configurable via `params`) |
| **database** | N | PostgreSQL with pgbench loop on a data disk | `pgbench -c 10 -j 2 -T 300` (configurable via `params`) |

**Multi-VM benchmarks** — server/client pairs coordinated via a ClusterIP Service:

| Workload | VMs | Description | Tools |
|----------|-----|-------------|-------|
| **network** | N × 2 | Bidirectional throughput | `iperf3 --bidir` on port 5201 |
| **network** | N × 2 | Bidirectional throughput | `iperf3 -P 4 -t 60 --bidir` on port 5201 (configurable via `params`) |
| **tps** | N × 2 | TCP request/response + HTTP file transfer | `netperf` (12865/12866) + Python HTTP server (8080) |

**Chaos engineering** — inject failures inside the VM to test resilience. ⚠️ See [docs/chaos-workloads.md](docs/chaos-workloads.md) before deploying:
Expand Down
10 changes: 5 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,11 +358,11 @@ classDiagram

| Workload | VM Count | Data Volume | K8s Service | Packages | Workload Tool |
|----------|----------|-------------|-------------|----------|---------------|
| CPU | N (configurable) | No | No | stress-ng | `stress-ng --cpu 0 --cpu-method all` |
| Memory | N (configurable) | No | No | stress-ng | `stress-ng --vm 1 --vm-bytes 80% --vm-method all` |
| Disk | N (configurable) | Yes (`/mnt/data`) | No | fio | Mixed R/W + sequential write profiles |
| Database | N (configurable) | Yes (`/var/lib/pgsql/data`) | No | postgresql-server | `pgbench -c 10 -j 2 -T 300` loop |
| Network | N × 2 (server + client) | No | Yes — `virtwork-iperf3-server` :5201 | iperf3 | `iperf3 -s` / `iperf3 -c ... --bidir` |
| CPU | N (configurable) | No | No | stress-ng | `stress-ng --cpu 0 --cpu-load 100 --cpu-method all` (defaults; tunable via `params`) |
| Memory | N (configurable) | No | No | stress-ng | `stress-ng --vm 1 --vm-bytes 80% --vm-method all` (defaults; tunable via `params`) |
| Disk | N (configurable) | Yes (`/mnt/data`) | No | fio | Mixed R/W + sequential write profiles (defaults; tunable via `params`) |
| Database | N (configurable) | Yes (`/var/lib/pgsql/data`) | No | postgresql-server | `pgbench -c 10 -j 2 -T 300` loop (defaults; tunable via `params`) |
| Network | N × 2 (server + client) | No | Yes — `virtwork-iperf3-server` :5201 | iperf3 | `iperf3 -s` / `iperf3 -c ... -P 4 -t 60 --bidir` (defaults; tunable via `params`) |
| TPS | N × 2 (server + client) | No | Yes — `virtwork-tps-server` :12865 / :12866 / :8080 | netperf, python3 | `netperf -t TCP_RR` + curl HTTP file fetch loop |
| Chaos-disk | N (configurable) | Yes (`/mnt/data`) | No | (golden image: `fallocate`, `dd`) | Fill to target percent, sleep, release, repeat |
| Chaos-network | N (configurable) | No | No | iproute-tc (+ `sch_netem` kernel module) | `tc qdisc add ... netem delay 100ms loss 5%` |
Expand Down
39 changes: 36 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,35 @@ workloads:
vm_count: 2
cpu_cores: 4
memory: 4Gi
params:
cpu-load-percent: "50" # default 100
cpu-method: "matrixprod" # default all
memory:
vm_count: 1 # enabled by default when not specified
params:
memory-percent: "60" # default 80
vm-stressors: "2" # default 1
vm-method: "flip" # default all
disk:
enabled: false # skip this workload entirely
params:
block-size-rw: "8k" # default 4k
block-size-seq: "256k" # default 128k
rwmixread: "50" # default 70
numjobs: "8" # default 4
runtime: "600" # default 300
database:
cpu_cores: 2
memory: 4Gi
params:
scale-factor: "100" # default 50
clients: "20" # default 10
duration: "600" # default 300
network:
vm_count: 1 # creates 1 server + 1 client = 2 VMs
params:
parallel-streams: "8" # default 4
test-duration: "120" # default 60
tps:
vm_count: 1 # creates 1 server + 1 client = 2 VMs
params:
Expand Down Expand Up @@ -215,8 +235,21 @@ Each workload's `params` block accepts string-valued keys. The current parameter
| **chaos-process** | `signal` | `SIGTERM` | Signal sent to victims |
| **chaos-process** | `interval` | `30` | Seconds between kills |
| **chaos-process** | `min-pid` | `1000` | Minimum PID considered eligible |

Workloads without entries above accept no per-workload `params` today (cpu, memory, disk, database, network).
| **cpu** | `cpu-load-percent` | `100` | Target CPU load percentage for stress-ng (`--cpu-load`) |
| **cpu** | `cpu-method` | `all` | CPU stressor method for stress-ng (`--cpu-method`) |
| **memory** | `memory-percent` | `80` | Target memory usage percentage (`--vm-bytes`) |
| **memory** | `vm-stressors` | `1` | Number of VM worker stressors (`--vm`) |
| **memory** | `vm-method` | `all` | Memory stressor method for stress-ng (`--vm-method`) |
| **disk** | `block-size-rw` | `4k` | Block size for the random read/write fio profile |
| **disk** | `block-size-seq` | `128k` | Block size for the sequential write fio profile |
| **disk** | `rwmixread` | `70` | Read percentage in the mixed read/write fio profile |
| **disk** | `numjobs` | `4` | Number of parallel fio jobs for the mixed read/write profile |
| **disk** | `runtime` | `300` | Runtime in seconds for each fio profile |
| **database** | `scale-factor` | `50` | pgbench initialization scale factor (`-s`) |
| **database** | `clients` | `10` | Number of concurrent pgbench clients (`-c`) |
| **database** | `duration` | `300` | Seconds per pgbench benchmark run (`-T`) |
| **network** | `parallel-streams` | `4` | Number of parallel iperf3 streams (`-P`) |
| **network** | `test-duration` | `60` | Seconds per iperf3 test run (`-t`) |

---

Expand Down Expand Up @@ -293,7 +326,7 @@ The `--no-audit` flag is checked separately by `initAuditor` in `cmd/virtwork/ma

- All config field definitions live in `internal/config/config.go` — `WorkloadConfig` struct, `Config` struct, `SetDefaults`, `BindFlags`, `LoadConfig`.
- The mapstructure tag on each field is the YAML key name (e.g., `mapstructure:"data-disk-size"` → YAML key `data_disk_size` after the `_`/`-` replacement). Match this convention when adding new fields.
- Per-workload `params` are surfaced into the workload as `WorkloadConfig.Params map[string]string`. New chaos / multi-VM knobs go here so they're uniformly addressable from YAML.
- Per-workload `params` are surfaced into the workload as `WorkloadConfig.Params map[string]string`. All workloads use this mechanism for tunable knobs so they're uniformly addressable from YAML.
- When adding a new env var, prefer letting Viper bind it automatically rather than hand-rolling `os.Getenv` (see `VIRTWORK_SSH_AUTHORIZED_KEYS` in `resolveSSHKeys` for the one current exception, driven by the comma-split list semantics).
- Update this document, the ConfigMap default list, and the per-workload `params` table whenever you add a new knob.

Expand Down
52 changes: 51 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ Pattern:
5. Set `RequiresService()` to `true` and provide a `ServiceSpec()` selecting server VMs by the `virtwork/role: server` label that the orchestrator applies automatically.
6. Clients reach servers via the in-cluster DNS name `<service-name>.<namespace>.svc.cluster.local` — never poll for pod IPs.

The canonical references are `internal/workloads/network.go` (simplest — one port, iperf3) and `internal/workloads/tps.go` (multi-port Service with configurable `Params` for `file-size`, `iterations`, `duration`).
The canonical references are `internal/workloads/network.go` (simplest — one port, iperf3) and `internal/workloads/tps.go` (multi-port Service). All workloads support configurable `Params` via the getter-with-default pattern (see [Configurable Params](#going-further-configurable-params) below).

### Going Further: Storage-Backed Workloads

Expand All @@ -604,6 +604,56 @@ If your workload needs persistent storage inside the VM:

Reference workloads: `disk.go` (single fio mount), `database.go` (PostgreSQL data dir), `chaos_disk.go` (fill/release loop). All three follow the same pattern.

### Going Further: Configurable Params

All workloads expose tunable knobs through `WorkloadConfig.Params map[string]string`. Users set these in YAML config under `workloads.<name>.params`. The standard pattern is a nil-safe getter method with a hardcoded default:

```go
func (w *MyWorkload) concurrency() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["concurrency"]; ok && val != "" {
return val
}
}
return "10"
}
```

Then use the getter when building cloud-init content. Convert your systemd unit constant to a template with `%s` placeholders and interpolate with `fmt.Sprintf`:

```go
const mySystemdUnitTemplate = `[Unit]
Description=My workload

[Service]
Type=simple
ExecStart=/usr/bin/my-tool --concurrency %s --duration %s
Restart=always

[Install]
WantedBy=multi-user.target
`

func (w *MyWorkload) CloudInitUserdata() (string, error) {
unit := fmt.Sprintf(mySystemdUnitTemplate, w.concurrency(), w.duration())
return w.BuildCloudConfig(CloudConfigOpts{
// ...
WriteFiles: []WriteFile{{
Path: "/etc/systemd/system/virtwork-my-workload.service",
Content: unit,
}},
})
}
```

Every workload should have a `Context("param wiring")` test block with three cases:

1. **Nil params** — `Params` field omitted, output contains default values
2. **Full override** — all param keys set, output reflects custom values
3. **Partial override** — some keys set, unset keys fall back to defaults

See `internal/workloads/cpu_test.go` for the simplest example, or `disk_test.go` for a workload with multiple output files to verify. Document new param keys in `docs/configuration.md` — both in the YAML example and the params table.

### Going Further: Structured Logging

The `internal/logging` package provides a shared `*slog.Logger` returned by `NewLogger(w io.Writer, verbose bool)`. Use it instead of `fmt.Fprintf` or `log.Printf` in any code path under `cmd/` or in packages that perform I/O (`internal/wait` is the current example):
Expand Down
4 changes: 2 additions & 2 deletions docs/guide/01-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ write_files:

[Service]
Type=simple
ExecStart=/usr/bin/stress-ng --cpu 0 --cpu-method all --timeout 0
ExecStart=/usr/bin/stress-ng --cpu 0 --cpu-load 100 --cpu-method all --timeout 0
Restart=always
RestartSec=10

Expand All @@ -72,7 +72,7 @@ runcmd:
- virtwork-cpu.service
```

This is a standard [cloud-init](https://cloudinit.readthedocs.io/) configuration. When the VM boots, cloud-init will install `stress-ng` from the package manager, write the systemd unit file, and enable the service. The workload starts automatically.
This is a standard [cloud-init](https://cloudinit.readthedocs.io/) configuration. When the VM boots, cloud-init will install `stress-ng` from the package manager, write the systemd unit file, and enable the service. The workload starts automatically. The values shown above are defaults — all core workloads accept per-workload `params` to override them (see [configuration.md](../configuration.md#per-workload-params-keys)).

If SSH credentials were configured, a `users` section is appended with the SSH public keys and/or password.

Expand Down
5 changes: 4 additions & 1 deletion docs/guide/03-adding-a-workload.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ return w.BuildCloudConfig(CloudConfigOpts{

### Making It Multi-VM

If your workload needs more than one role of VM (a server and one or more clients, for example), implement the `MultiVMWorkload` interface. The two canonical references are `internal/workloads/network.go` (simplest — one Service port, iperf3) and `internal/workloads/tps.go` (multi-port Service with configurable `Params` for `file-size`, `iterations`, `duration`).
If your workload needs more than one role of VM (a server and one or more clients, for example), implement the `MultiVMWorkload` interface. The two canonical references are `internal/workloads/network.go` (simplest — one Service port, iperf3) and `internal/workloads/tps.go` (multi-port Service). All workloads support configurable `Params` via the getter-with-default pattern — see [development.md](../development.md#going-further-configurable-params).

1. Add a `Namespace` field to your struct — the client needs it to build the server's in-cluster DNS name.
2. Implement `RoleDistribution() []RoleSpec` — return a slice of `RoleSpec{Role: "server", VMCount: 1}` entries declaring how many VMs each role needs.
Expand Down Expand Up @@ -552,6 +552,9 @@ Before submitting a new workload, verify:
- [ ] Packages used are available in Fedora's default repos (or pre-installed in the golden image, if applicable)
- [ ] Systemd unit has `Restart=always` and `WantedBy=multi-user.target`
- [ ] Tests cover: Name, packages, systemd unit content, valid YAML, VMResources, defaults for optional methods
- [ ] Tunable values exposed as `params` with getter-with-default methods (see [development.md](../development.md#going-further-configurable-params))
- [ ] Param wiring tests: nil params → defaults, full override, partial override
- [ ] New param keys documented in `docs/configuration.md` (YAML example + params table)
- [ ] Registered in `DefaultRegistry()` (`internal/workloads/registry.go`) with a factory function
- [ ] Existing registry/orchestration test counts updated
- [ ] `go test ./...` passes
Expand Down
8 changes: 4 additions & 4 deletions docs/virtwork-vs-kube-burner.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ Workloads deployed inside VMs:

| Workload | Tool | What It Generates |
|---|---|---|
| `cpu` | `stress-ng --cpu 0 --cpu-method all` | Continuous CPU pressure across all cores |
| `memory` | `stress-ng --vm 1 --vm-bytes 80%` | Sustained memory pressure at 80% |
| `database` | PostgreSQL + `pgbench -c 10 -j 2 -T 300` | Realistic OLTP database transactions |
| `network` | `iperf3 --bidir` (server + client VM pairs) | Bidirectional throughput between VMs |
| `cpu` | `stress-ng --cpu 0 --cpu-load 100 --cpu-method all` | Continuous CPU pressure across all cores (configurable via `params`) |
| `memory` | `stress-ng --vm 1 --vm-bytes 80% --vm-method all` | Sustained memory pressure (configurable via `params`) |
| `database` | PostgreSQL + `pgbench -c 10 -j 2 -T 300` | Realistic OLTP database transactions (configurable via `params`) |
| `network` | `iperf3 -P 4 -t 60 --bidir` (server + client VM pairs) | Bidirectional throughput between VMs (configurable via `params`) |
| `tps` | `netperf` + `curl` (server + client VM pairs) | Multi-port HTTP throughput with configurable file size, iterations, and duration |
| `disk` | `fio` with mixed random + sequential profiles | Mixed I/O patterns on a dedicated data disk |
| `chaos-disk` | `fallocate`/`dd` fill-release loop | Sustained disk-pressure events on a data disk |
Expand Down
Loading