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
25 changes: 23 additions & 2 deletions cmd/virtwork/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,22 @@ func runE(cmd *cobra.Command, args []string) error {
auditWorkloadIDs := make(map[string]int64) // workload name -> audit workload ID

for _, name := range workloadNames {
// Check if workload is explicitly disabled in YAML config
if fileCfg, ok := cfg.Workloads[name]; ok {
if fileCfg.Enabled != nil && !*fileCfg.Enabled {
_ = auditor.RecordEvent(ctx, execID, audit.EventRecord{
EventType: "workload_skipped",
Message: fmt.Sprintf("Workload %q disabled via config (enabled: false)", name),
})
logger.Info("workload skipped",
slog.String("workload", name),
slog.String("reason", "disabled in config"))
continue
}
}

wlCfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: vmCountFlag,
CPUCores: cfg.CPUCores,
Memory: cfg.Memory,
Expand Down Expand Up @@ -412,9 +426,16 @@ func runE(cmd *cobra.Command, args []string) error {
// Create services before VMs (DNS must resolve for client VMs)
servicesCreated := 0
for _, name := range workloadNames {
// Skip if workload is explicitly disabled
if fileCfg, ok := cfg.Workloads[name]; ok {
if fileCfg.Enabled != nil && !*fileCfg.Enabled {
continue
}
}

// Re-fetch workload to check service requirement
wlCfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: vmCountFlag,
CPUCores: cfg.CPUCores,
Memory: cfg.Memory,
Expand Down
87 changes: 77 additions & 10 deletions cmd/virtwork/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ var _ = Describe("Run orchestration", func() {
It("should build VM specs without cluster connection", func() {
registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand Down Expand Up @@ -332,7 +332,7 @@ var _ = Describe("Run orchestration", func() {
It("should print specs to stdout in dry-run", func() {
registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand Down Expand Up @@ -395,7 +395,7 @@ var _ = Describe("Run orchestration", func() {

registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand Down Expand Up @@ -443,7 +443,7 @@ var _ = Describe("Run orchestration", func() {

registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 2,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand All @@ -466,7 +466,7 @@ var _ = Describe("Run orchestration", func() {
It("should handle multi-VM workloads via type assertion", func() {
registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 2,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand Down Expand Up @@ -511,6 +511,73 @@ var _ = Describe("Run orchestration", func() {
})
})

var _ = Describe("Workload enabled field", func() {
Context("when workload is explicitly disabled in YAML", func() {
It("should skip disabled workloads", func() {
// This test verifies that setting enabled: false in YAML config
// causes the orchestrator to skip that workload entirely.

// We can't easily test the full orchestration without mocking,
// but we can verify the config parsing works correctly
// by checking that Enabled field is properly set from YAML.

// The actual skip logic is tested via manual verification and
// integration tests that check audit events.

cfg := config.WorkloadConfig{
Enabled: config.BoolPtr(false),
VMCount: 2,
CPUCores: 4,
Memory: "4Gi",
}

Expect(cfg.Enabled).NotTo(BeNil())
Expect(*cfg.Enabled).To(BeFalse())
})
})

Context("when workload is explicitly enabled in YAML", func() {
It("should include enabled workloads", func() {
cfg := config.WorkloadConfig{
Enabled: config.BoolPtr(true),
VMCount: 2,
CPUCores: 4,
Memory: "4Gi",
}

Expect(cfg.Enabled).NotTo(BeNil())
Expect(*cfg.Enabled).To(BeTrue())
})
})

Context("when enabled field is not set in YAML", func() {
It("should default to nil (treated as enabled)", func() {
cfg := config.WorkloadConfig{
VMCount: 2,
CPUCores: 4,
Memory: "4Gi",
}

Expect(cfg.Enabled).To(BeNil())
// When nil, the orchestrator treats it as enabled
})
})

Context("BoolPtr helper function", func() {
It("should create pointer to true", func() {
ptr := config.BoolPtr(true)
Expect(ptr).NotTo(BeNil())
Expect(*ptr).To(BeTrue())
})

It("should create pointer to false", func() {
ptr := config.BoolPtr(false)
Expect(ptr).NotTo(BeNil())
Expect(*ptr).To(BeFalse())
})
})
})

var _ = Describe("Cleanup command", func() {
It("should delete managed resources", func() {
scheme := cluster.NewScheme()
Expand Down Expand Up @@ -566,7 +633,7 @@ var _ = Describe("CLI end-to-end scenarios", func() {

registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand All @@ -582,7 +649,7 @@ var _ = Describe("CLI end-to-end scenarios", func() {
It("should print VM specs to stdout", func() {
registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand Down Expand Up @@ -633,7 +700,7 @@ var _ = Describe("CLI end-to-end scenarios", func() {
totalVMs := 0
for _, name := range workloads.AllWorkloadNames() {
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand Down Expand Up @@ -703,7 +770,7 @@ var _ = Describe("DataVolume namespacing for multi-VM deployments", func() {
It("should return base DataVolume template name", func() {
registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 2,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand All @@ -730,7 +797,7 @@ var _ = Describe("DataVolume namespacing for multi-VM deployments", func() {
It("should return base DataVolume template name", func() {
registry := workloads.DefaultRegistry()
cfg := config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 2,
CPUCores: constants.DefaultCPUCores,
Memory: constants.DefaultMemory,
Expand Down
38 changes: 28 additions & 10 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,49 +131,67 @@ audit_db: /data/virtwork.db
# Per-workload overrides (everything optional; unspecified keys inherit globals)
workloads:
cpu:
enabled: true
enabled: true # explicitly enable (can be omitted; defaults to enabled)
vm_count: 2
cpu_cores: 4
memory: 4Gi
memory:
enabled: true
vm_count: 1
vm_count: 1 # enabled by default when not specified
disk:
enabled: true
enabled: false # skip this workload entirely
database:
enabled: true
cpu_cores: 2
memory: 4Gi
network:
enabled: true
vm_count: 1 # creates 1 server + 1 client = 2 VMs
tps:
enabled: true
vm_count: 1 # creates 1 server + 1 client = 2 VMs
params:
file-size: "50M" # default 10M
iterations: "10" # default 30
duration: "30" # default 60 (seconds per iteration)
chaos-disk:
enabled: true
params:
mount: /mnt/data # default /mnt/data
fill-percent: "80" # default 90
fill-sleep: "120" # default 60
release-sleep: "60" # default 30
chaos-network:
enabled: true
params:
latency-ms: "250" # default 100
packet-loss-percent: "10" # default 5.0
chaos-process:
enabled: true
params:
signal: "SIGKILL" # default SIGTERM
interval: "15" # default 30
min-pid: "500" # default 1000
```

### Per-workload `enabled` field

The `enabled` field controls whether a workload is deployed when listed in the `--workloads` flag. This provides a declarative way to disable workloads in YAML config without modifying command-line flags.

- **`enabled: true`** — workload is deployed (explicit)
- **`enabled: false`** — workload is skipped entirely; no VMs, services, or resources created
- **Field omitted** — workload is enabled by default (treated as `true`)

**Precedence:** The `--workloads` flag determines the candidate set; the YAML `enabled` field then filters it. For example:

```bash
virtwork run --workloads cpu,disk,network --config config.yaml
```

With `config.yaml`:
```yaml
workloads:
disk:
enabled: false
```

**Result:** Only `cpu` and `network` are deployed. `disk` is skipped despite being in the `--workloads` flag.

**Audit event:** When a workload is skipped due to `enabled: false`, an audit event of type `workload_skipped` is recorded with the message `Workload "name" disabled via config (enabled: false)`.

### Per-workload `params` keys

Each workload's `params` block accepts string-valued keys. The current parameters per workload:
Expand Down
4 changes: 2 additions & 2 deletions internal/audit/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ var _ = Describe("SQLiteAuditor", func() {
WaitForReady: true,
ReadyTimeoutSeconds: 300,
Workloads: map[string]config.WorkloadConfig{
"cpu": {Enabled: true, VMCount: 2, CPUCores: 4, Memory: "4Gi"},
"database": {Enabled: true, VMCount: 1, CPUCores: 2, Memory: "2Gi"},
"cpu": {Enabled: config.BoolPtr(true), VMCount: 2, CPUCores: 4, Memory: "4Gi"},
"database": {Enabled: config.BoolPtr(true), VMCount: 1, CPUCores: 2, Memory: "2Gi"},
},
}
})
Expand Down
8 changes: 7 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ import (

// WorkloadConfig holds per-workload configuration.
type WorkloadConfig struct {
Enabled bool `mapstructure:"enabled"`
Enabled *bool `mapstructure:"enabled"`
VMCount int `mapstructure:"vm-count"`
CPUCores int `mapstructure:"cpu-cores"`
Memory string `mapstructure:"memory"`
Params map[string]string `mapstructure:"params"`
}

// BoolPtr returns a pointer to the provided bool value.
// Useful for setting WorkloadConfig.Enabled in tests and code.
func BoolPtr(b bool) *bool {
return &b
}

// Config holds the complete application configuration.
type Config struct {
Namespace string `mapstructure:"namespace"`
Expand Down
6 changes: 4 additions & 2 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,13 @@ workloads:
cfg, err := config.LoadConfig(cmd)
Expect(err).NotTo(HaveOccurred())
Expect(cfg.Workloads).To(HaveKey("cpu"))
Expect(cfg.Workloads["cpu"].Enabled).To(BeTrue())
Expect(cfg.Workloads["cpu"].Enabled).NotTo(BeNil())
Expect(*cfg.Workloads["cpu"].Enabled).To(BeTrue())
Expect(cfg.Workloads["cpu"].VMCount).To(Equal(2))
Expect(cfg.Workloads["cpu"].CPUCores).To(Equal(4))
Expect(cfg.Workloads["cpu"].Memory).To(Equal("4Gi"))
Expect(cfg.Workloads["disk"].Enabled).To(BeFalse())
Expect(cfg.Workloads["disk"].Enabled).NotTo(BeNil())
Expect(*cfg.Workloads["disk"].Enabled).To(BeFalse())
})

It("should load workload params from YAML", func() {
Expand Down
6 changes: 3 additions & 3 deletions internal/workloads/chaos_disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var _ = Describe("ChaosDiskWorkload", func() {

BeforeEach(func() {
w = workloads.NewChaosDiskWorkload(config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: 2,
Memory: "2Gi",
Expand Down Expand Up @@ -227,7 +227,7 @@ var _ = Describe("ChaosDiskWorkload", func() {

It("should wire custom params from WorkloadConfig.Params", func() {
custom := workloads.NewChaosDiskWorkload(config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: 2,
Memory: "2Gi",
Expand Down Expand Up @@ -271,7 +271,7 @@ var _ = Describe("ChaosDiskWorkload", func() {

It("should use defaults for missing individual params", func() {
partial := workloads.NewChaosDiskWorkload(config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: 2,
Memory: "2Gi",
Expand Down
6 changes: 3 additions & 3 deletions internal/workloads/chaos_network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var _ = Describe("ChaosNetworkWorkload", func() {

BeforeEach(func() {
w = workloads.NewChaosNetworkWorkload(config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: 1,
Memory: "1Gi",
Expand Down Expand Up @@ -155,7 +155,7 @@ var _ = Describe("ChaosNetworkWorkload", func() {

It("should wire custom params from WorkloadConfig.Params", func() {
custom := workloads.NewChaosNetworkWorkload(config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: 1,
Memory: "1Gi",
Expand Down Expand Up @@ -186,7 +186,7 @@ var _ = Describe("ChaosNetworkWorkload", func() {

It("should use defaults for missing individual params", func() {
partial := workloads.NewChaosNetworkWorkload(config.WorkloadConfig{
Enabled: true,
Enabled: config.BoolPtr(true),
VMCount: 1,
CPUCores: 1,
Memory: "1Gi",
Expand Down
Loading
Loading