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
6 changes: 6 additions & 0 deletions internal/orchestrator/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ func (ro *RunOrchestrator) planVMs(
}
}

if len(wlCfg.Params) > 0 {
if err := registry.ValidateParams(name, wlCfg.Params); err != nil {
return nil, nil, nil, nil, fmt.Errorf("invalid params for workload %q: %w", name, err)
}
}

w, err := registry.Get(name, wlCfg, registryOpts...)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("creating workload %q: %w", name, err)
Expand Down
38 changes: 38 additions & 0 deletions internal/orchestrator/orchestrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,44 @@ var _ = Describe("RunOrchestrator", func() {
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("nonexistent"))
})

It("should return error for invalid params", func() {
cfg.DryRun = true
cfg.Workloads = map[string]config.WorkloadConfig{
"cpu": {
Params: map[string]string{
"cpu-load-percent": "banana",
},
},
}

logger := logging.NewLogger(buf, false)
ro := orchestrator.NewRunOrchestrator(logger, nil, cfg, auditor, buf)

_, err := ro.Run(ctx, 0, "test-run-id", []string{"cpu"}, 1)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("invalid params"))
Expect(err.Error()).To(ContainSubstring("must be an integer"))
})

It("should return error for unknown param keys", func() {
cfg.DryRun = true
cfg.Workloads = map[string]config.WorkloadConfig{
"cpu": {
Params: map[string]string{
"bogus-key": "42",
},
},
}

logger := logging.NewLogger(buf, false)
ro := orchestrator.NewRunOrchestrator(logger, nil, cfg, auditor, buf)

_, err := ro.Run(ctx, 0, "test-run-id", []string{"cpu"}, 1)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("unknown param"))
Expect(err.Error()).To(ContainSubstring("bogus-key"))
})
})

Context("normal mode with fake client", func() {
Expand Down
53 changes: 13 additions & 40 deletions internal/workloads/chaos_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ RestartSec=10
WantedBy=multi-user.target
`

// ChaosDiskParamSchema declares the configurable params for the chaos-disk workload.
var ChaosDiskParamSchema = ParamSchema{
{Key: "mount", Type: ParamString, Default: "/mnt/data", Desc: "Mount point for the data disk"},
{Key: "fill-percent", Type: ParamInt, Default: "90", Desc: "Target fill percentage of the data disk"},
{Key: "fill-sleep", Type: ParamInt, Default: "60", Desc: "Seconds to hold disk at fill level before releasing"},
{Key: "release-sleep", Type: ParamInt, Default: "30", Desc: "Seconds to wait after releasing before re-filling"},
}

// ChaosDiskWorkload generates cloud-init userdata for a disk fill/release chaos
// workload using fallocate and dd.
type ChaosDiskWorkload struct {
Expand All @@ -71,6 +79,7 @@ func NewChaosDiskWorkload(
return &ChaosDiskWorkload{
BaseWorkload: BaseWorkload{
Config: cfg,
ParamSchema: ChaosDiskParamSchema,
SSHUser: sshUser,
SSHPassword: sshPassword,
SSHAuthorizedKeys: sshKeys,
Expand All @@ -79,42 +88,6 @@ func NewChaosDiskWorkload(
}
}

func (w *ChaosDiskWorkload) mount() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["mount"]; ok && val != "" {
return val
}
}
return "/mnt/data"
}

func (w *ChaosDiskWorkload) fillPercent() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["fill-percent"]; ok && val != "" {
return val
}
}
return "90"
}

func (w *ChaosDiskWorkload) fillSleep() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["fill-sleep"]; ok && val != "" {
return val
}
}
return "60"
}

func (w *ChaosDiskWorkload) releaseSleep() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["release-sleep"]; ok && val != "" {
return val
}
}
return "30"
}

// Name returns "chaos-disk".
func (w *ChaosDiskWorkload) Name() string {
return "chaos-disk"
Expand All @@ -123,12 +96,12 @@ func (w *ChaosDiskWorkload) Name() string {
// CloudInitUserdata returns cloud-init YAML that writes a fill/release script
// and a systemd service that runs it in a loop.
func (w *ChaosDiskWorkload) CloudInitUserdata() (string, error) {
mountPoint := w.mount()
mountPoint := w.GetParam("mount")
script := fmt.Sprintf(chaosDiskScriptTemplate,
mountPoint,
w.fillPercent(),
w.releaseSleep(),
w.fillSleep())
w.GetParam("fill-percent"),
w.GetParam("release-sleep"),
w.GetParam("fill-sleep"))
unit := chaosDiskSystemdUnit

return w.BuildCloudConfig(CloudConfigOpts{
Expand Down
29 changes: 9 additions & 20 deletions internal/workloads/chaos_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ RestartSec=10
WantedBy=multi-user.target
`

// ChaosNetworkParamSchema declares the configurable params for the chaos-network workload.
var ChaosNetworkParamSchema = ParamSchema{
{Key: "latency-ms", Type: ParamInt, Default: "100", Desc: "Added latency in milliseconds via tc/netem"},
{Key: "packet-loss-percent", Type: ParamString, Default: "5.0", Desc: "Packet loss percentage via tc/netem"},
}

// ChaosNetworkWorkload generates cloud-init userdata for network chaos injection
// using tc (traffic control) and netem (network emulation).
type ChaosNetworkWorkload struct {
Expand All @@ -61,31 +67,14 @@ func NewChaosNetworkWorkload(
return &ChaosNetworkWorkload{
BaseWorkload: BaseWorkload{
Config: cfg,
ParamSchema: ChaosNetworkParamSchema,
SSHUser: sshUser,
SSHPassword: sshPassword,
SSHAuthorizedKeys: sshKeys,
},
}
}

func (w *ChaosNetworkWorkload) latencyMs() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["latency-ms"]; ok && val != "" {
return val
}
}
return "100"
}

func (w *ChaosNetworkWorkload) packetLossPercent() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["packet-loss-percent"]; ok && val != "" {
return val
}
}
return "5.0"
}

// Name returns "chaos-network".
func (w *ChaosNetworkWorkload) Name() string {
return "chaos-network"
Expand All @@ -96,8 +85,8 @@ func (w *ChaosNetworkWorkload) Name() string {
// (sch_netem); the start script runs modprobe as a fallback for non-golden images.
func (w *ChaosNetworkWorkload) CloudInitUserdata() (string, error) {
startScript := fmt.Sprintf(chaosNetworkStartScript,
w.latencyMs(),
w.packetLossPercent())
w.GetParam("latency-ms"),
w.GetParam("packet-loss-percent"))

return w.BuildCloudConfig(CloudConfigOpts{
Packages: []string{"iproute-tc"},
Expand Down
41 changes: 11 additions & 30 deletions internal/workloads/chaos_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ StandardError=journal
WantedBy=multi-user.target
`

// ChaosProcessParamSchema declares the configurable params for the chaos-process workload.
var ChaosProcessParamSchema = ParamSchema{
{Key: "signal", Type: ParamString, Default: "SIGTERM", Desc: "Signal to send to target processes"},
{Key: "interval", Type: ParamInt, Default: "30", Desc: "Seconds between chaos actions"},
{Key: "min-pid", Type: ParamInt, Default: "1000", Desc: "Minimum PID to target (skips kernel threads)"},
}

// ChaosProcessWorkload generates cloud-init userdata for a process chaos workload
// that randomly kills non-essential processes to test resilience.
type ChaosProcessWorkload struct {
Expand All @@ -139,40 +146,14 @@ func NewChaosProcessWorkload(
return &ChaosProcessWorkload{
BaseWorkload: BaseWorkload{
Config: cfg,
ParamSchema: ChaosProcessParamSchema,
SSHUser: sshUser,
SSHPassword: sshPassword,
SSHAuthorizedKeys: sshKeys,
},
}
}

func (w *ChaosProcessWorkload) signal() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["signal"]; ok && val != "" {
return val
}
}
return "SIGTERM"
}

func (w *ChaosProcessWorkload) interval() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["interval"]; ok && val != "" {
return val
}
}
return "30"
}

func (w *ChaosProcessWorkload) minPid() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["min-pid"]; ok && val != "" {
return val
}
}
return "1000"
}

// Name returns "chaos-process".
func (w *ChaosProcessWorkload) Name() string {
return "chaos-process"
Expand All @@ -182,9 +163,9 @@ func (w *ChaosProcessWorkload) Name() string {
// and runs it as a systemd service.
func (w *ChaosProcessWorkload) CloudInitUserdata() (string, error) {
unit := fmt.Sprintf(chaosProcessSystemdUnitTemplate,
w.signal(),
w.interval(),
w.minPid())
w.GetParam("signal"),
w.GetParam("interval"),
w.GetParam("min-pid"))

return w.BuildCloudConfig(CloudConfigOpts{
Packages: []string{"procps-ng"},
Expand Down
32 changes: 13 additions & 19 deletions internal/workloads/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ RestartSec=10
WantedBy=multi-user.target
`

// CPUParamSchema declares the configurable params for the CPU workload.
var CPUParamSchema = ParamSchema{
{
Key: "cpu-load-percent",
Type: ParamInt,
Default: "100",
Desc: "Target CPU load percentage for stress-ng (--cpu-load)",
},
{Key: "cpu-method", Type: ParamString, Default: "all", Desc: "CPU stressor method for stress-ng (--cpu-method)"},
}

// CPUWorkload generates cloud-init userdata for a continuous CPU stress workload
// using stress-ng.
type CPUWorkload struct {
Expand All @@ -38,31 +49,14 @@ func NewCPUWorkload(
return &CPUWorkload{
BaseWorkload: BaseWorkload{
Config: cfg,
ParamSchema: CPUParamSchema,
SSHUser: sshUser,
SSHPassword: sshPassword,
SSHAuthorizedKeys: sshKeys,
},
}
}

func (w *CPUWorkload) cpuLoadPercent() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["cpu-load-percent"]; ok && val != "" {
return val
}
}
return "100"
}

func (w *CPUWorkload) cpuMethod() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["cpu-method"]; ok && val != "" {
return val
}
}
return "all"
}

// Name returns "cpu".
func (w *CPUWorkload) Name() string {
return "cpu"
Expand All @@ -71,7 +65,7 @@ func (w *CPUWorkload) Name() string {
// CloudInitUserdata returns cloud-init YAML that installs stress-ng and runs a
// continuous CPU stress workload via systemd.
func (w *CPUWorkload) CloudInitUserdata() (string, error) {
unit := fmt.Sprintf(cpuSystemdUnitTemplate, w.cpuLoadPercent(), w.cpuMethod())
unit := fmt.Sprintf(cpuSystemdUnitTemplate, w.GetParam("cpu-load-percent"), w.GetParam("cpu-method"))
return w.BuildCloudConfig(CloudConfigOpts{
Packages: []string{"stress-ng"},
WriteFiles: []WriteFile{
Expand Down
39 changes: 10 additions & 29 deletions internal/workloads/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ RestartSec=10
WantedBy=multi-user.target
`

// DatabaseParamSchema declares the configurable params for the database workload.
var DatabaseParamSchema = ParamSchema{
{Key: "scale-factor", Type: ParamInt, Default: "50", Desc: "pgbench scale factor (-s)"},
{Key: "clients", Type: ParamInt, Default: "10", Desc: "Number of concurrent pgbench clients (-c)"},
{Key: "duration", Type: ParamInt, Default: "300", Desc: "pgbench run duration in seconds (-T)"},
}

// DatabaseWorkload generates cloud-init userdata for a PostgreSQL database
// benchmark workload using pgbench. It formats a data disk, initializes
// PostgreSQL, creates a pgbench database at scale 50, and runs continuous
Expand All @@ -105,6 +112,7 @@ func NewDatabaseWorkload(
return &DatabaseWorkload{
BaseWorkload: BaseWorkload{
Config: cfg,
ParamSchema: DatabaseParamSchema,
SSHUser: sshUser,
SSHPassword: sshPassword,
SSHAuthorizedKeys: sshKeys,
Expand All @@ -113,33 +121,6 @@ func NewDatabaseWorkload(
}
}

func (w *DatabaseWorkload) scaleFactor() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["scale-factor"]; ok && val != "" {
return val
}
}
return "50"
}

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

func (w *DatabaseWorkload) duration() string {
if w.Config.Params != nil {
if val, ok := w.Config.Params["duration"]; ok && val != "" {
return val
}
}
return "300"
}

// Name returns "database".
func (w *DatabaseWorkload) Name() string {
return "database"
Expand All @@ -149,8 +130,8 @@ func (w *DatabaseWorkload) Name() string {
// a setup script for one-time database initialization, and creates a systemd
// service that runs continuous pgbench benchmarks.
func (w *DatabaseWorkload) CloudInitUserdata() (string, error) {
setupScript := fmt.Sprintf(dbSetupScriptTemplate, w.scaleFactor())
serviceUnit := fmt.Sprintf(dbSystemdUnitTemplate, w.clients(), w.duration())
setupScript := fmt.Sprintf(dbSetupScriptTemplate, w.GetParam("scale-factor"))
serviceUnit := fmt.Sprintf(dbSystemdUnitTemplate, w.GetParam("clients"), w.GetParam("duration"))
return w.BuildCloudConfig(CloudConfigOpts{
Packages: []string{"postgresql-server"},
WriteFiles: []WriteFile{
Expand Down
Loading
Loading