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
3 changes: 3 additions & 0 deletions cmd/virtwork/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ func runE(cmd *cobra.Command, args []string) error {
if fileCfg.VMCount > 0 {
wlCfg.VMCount = fileCfg.VMCount
}
if len(fileCfg.Params) > 0 {
wlCfg.Params = fileCfg.Params
}
}

w, err := registry.Get(name, wlCfg, registryOpts...)
Expand Down
6 changes: 3 additions & 3 deletions cmd/virtwork/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ var _ = Describe("CLI end-to-end scenarios", func() {

Context("when running with default arguments", func() {
It("should create VMs for all workloads", func() {
// Default run creates 6 VMs: cpu=1 + memory=1 + disk=1 + database=1 + network=2
// Default run creates 8 VMs: cpu=1 + memory=1 + disk=1 + database=1 + network=2 + tps=2
registry := workloads.DefaultRegistry()
totalVMs := 0
for _, name := range workloads.AllWorkloadNames {
Expand All @@ -643,8 +643,8 @@ var _ = Describe("CLI end-to-end scenarios", func() {
Expect(err).NotTo(HaveOccurred())
totalVMs += w.VMCount()
}
// cpu=1 + database=1 + disk=1 + memory=1 + network=2 = 6
Expect(totalVMs).To(Equal(6))
// cpu=1 + database=1 + disk=1 + memory=1 + network=2 + tps=2 = 8
Expect(totalVMs).To(Equal(8))
})
})

Expand Down
8 changes: 5 additions & 3 deletions docs/guide/02-deploying-workloads.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ This creates 3 VMs, 3 cloud-init secrets, and 0 services:
virtwork run
```

With no `--workloads` flag, all five workload types deploy. This creates **6 VMs** total — the network workload creates a server/client pair:
With no `--workloads` flag, all workload types deploy. This creates **8 VMs** total — the network and tps workloads each create server/client pairs:

| VM Name | Workload | Role |
|---------|----------|------|
Expand All @@ -210,8 +210,10 @@ With no `--workloads` flag, all five workload types deploy. This creates **6 VMs
| `virtwork-memory-0` | Memory | — |
| `virtwork-network-server-0` | Network | server |
| `virtwork-network-client-0` | Network | client |
| `virtwork-tps-server-0` | TPS | server |
| `virtwork-tps-client-0` | TPS | client |

The network workload also creates a `virtwork-iperf3-server` ClusterIP Service on port 5201.
The network workload creates a `virtwork-iperf3-server` ClusterIP Service on port 5201. The tps workload creates a `virtwork-tps-server` ClusterIP Service on ports 12865 (netperf control), 12866 (netperf data), and 8080 (HTTP file transfer).

### Scaling up

Expand Down Expand Up @@ -444,4 +446,4 @@ oc set env deploy/virtwork VIRTWORK_COMMAND=run VIRTWORK_ARGS="--workloads cpu,m
| VMs stuck in `Provisioning` | CDI not installed or no default StorageClass | Install the OpenShift Virtualization operator and ensure a default StorageClass exists |
| Timeout waiting for readiness | Slow image pull or boot | Increase `--timeout` (default is 600s), or use `--no-wait` and check manually |
| Cloud-init failed inside VM | Package install failure or script error | SSH in and check `/var/log/cloud-init-output.log` |
| `unknown workload` error | Typo in `--workloads` | Available workloads: `cpu`, `database`, `disk`, `memory`, `network` |
| `unknown workload` error | Typo in `--workloads` | Available workloads: `cpu`, `database`, `disk`, `memory`, `network`, `tps` |
9 changes: 5 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import (

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

// Config holds the complete application configuration.
Expand Down
28 changes: 28 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,5 +362,33 @@ workloads:
Expect(cfg.Workloads["cpu"].Memory).To(Equal("4Gi"))
Expect(cfg.Workloads["disk"].Enabled).To(BeFalse())
})

It("should load workload params from YAML", func() {
tmpDir, err := os.MkdirTemp("", "virtwork-config-test-*")
Expect(err).NotTo(HaveOccurred())
defer func() {
if err := os.RemoveAll(tmpDir); err != nil {
log.Println("cleaning temporary directory failed")
}
}()

path := writeConfigFile(tmpDir, `
workloads:
tps:
enabled: true
vm-count: 2
params:
file-size: 200M
mode: file-transfer
`)
err1 := cmd.Flags().Set("config", path)
Expect(err1).NotTo(HaveOccurred())

cfg, err := config.LoadConfig(cmd)
Expect(err).NotTo(HaveOccurred())
Expect(cfg.Workloads).To(HaveKey("tps"))
Expect(cfg.Workloads["tps"].Params).To(HaveKeyWithValue("file-size", "200M"))
Expect(cfg.Workloads["tps"].Params).To(HaveKeyWithValue("mode", "file-transfer"))
})
})
})
12 changes: 6 additions & 6 deletions internal/wait/wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var _ = Describe("WaitForVMReady", func() {
},
}

var callCount int32
var callCount atomic.Int32
c := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(vmi).
Expand All @@ -67,7 +67,7 @@ var _ = Describe("WaitForVMReady", func() {
if err != nil {
return err
}
count := atomic.AddInt32(&callCount, 1)
count := callCount.Add(1)
if vmiObj, ok := obj.(*kubevirtv1.VirtualMachineInstance); ok {
if count >= 3 {
vmiObj.Status.Phase = kubevirtv1.Running
Expand All @@ -80,7 +80,7 @@ var _ = Describe("WaitForVMReady", func() {

err := wait.WaitForVMReady(ctx, c, "eventual-vm", "default", 5*time.Second, 10*time.Millisecond)
Expect(err).NotTo(HaveOccurred())
Expect(atomic.LoadInt32(&callCount)).To(BeNumerically(">=", int32(3)))
Expect(callCount.Load()).To(BeNumerically(">=", int32(3)))
})

It("should return error on timeout", func() {
Expand Down Expand Up @@ -109,12 +109,12 @@ var _ = Describe("WaitForVMReady", func() {
})

It("should succeed when VMI appears after initial not-found", func() {
var callCount int32
var callCount atomic.Int32
c := fake.NewClientBuilder().
WithScheme(scheme).
WithInterceptorFuncs(interceptor.Funcs{
Get: func(ctx context.Context, cl client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
count := atomic.AddInt32(&callCount, 1)
count := callCount.Add(1)
if count <= 2 {
return cl.Get(ctx, key, obj, opts...) // not found from empty store
}
Expand All @@ -131,7 +131,7 @@ var _ = Describe("WaitForVMReady", func() {

err := wait.WaitForVMReady(ctx, c, "delayed-vm", "default", 5*time.Second, 10*time.Millisecond)
Expect(err).NotTo(HaveOccurred())
Expect(atomic.LoadInt32(&callCount)).To(BeNumerically(">=", int32(3)))
Expect(callCount.Load()).To(BeNumerically(">=", int32(3)))
})

It("should respect context cancellation", func() {
Expand Down
5 changes: 4 additions & 1 deletion internal/workloads/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type WorkloadFactory func(config.WorkloadConfig, *RegistryOpts) Workload
type Registry map[string]WorkloadFactory

// AllWorkloadNames is a sorted list of all built-in workload names.
var AllWorkloadNames = []string{"cpu", "database", "disk", "memory", "network"}
var AllWorkloadNames = []string{"cpu", "database", "disk", "memory", "network", "tps"}

// DefaultRegistry returns a Registry pre-populated with all built-in workloads.
func DefaultRegistry() Registry {
Expand All @@ -74,6 +74,9 @@ func DefaultRegistry() Registry {
"network": func(cfg config.WorkloadConfig, opts *RegistryOpts) Workload {
return NewNetworkWorkload(cfg, opts.Namespace, opts.SSHUser, opts.SSHPassword, opts.SSHAuthorizedKeys)
},
"tps": func(cfg config.WorkloadConfig, opts *RegistryOpts) Workload {
return NewTPSWorkload(cfg, opts.Namespace, opts.SSHUser, opts.SSHPassword, opts.SSHAuthorizedKeys)
},
}
}

Expand Down
21 changes: 16 additions & 5 deletions internal/workloads/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ var _ = Describe("Registry", func() {
reg = workloads.DefaultRegistry()
})

It("should have 5 entries registered", func() {
Expect(reg.List()).To(HaveLen(5))
It("should have 6 entries registered", func() {
Expect(reg.List()).To(HaveLen(6))
})

It("should return CPU workload by name", func() {
Expand Down Expand Up @@ -90,7 +90,18 @@ var _ = Describe("Registry", func() {

It("should list all names sorted alphabetically", func() {
names := reg.List()
Expect(names).To(Equal([]string{"cpu", "database", "disk", "memory", "network"}))
Expect(names).To(Equal([]string{"cpu", "database", "disk", "memory", "network", "tps"}))
})

It("should return tps workload by name", func() {
w, err := reg.Get("tps", config.WorkloadConfig{
Enabled: true,
VMCount: 2,
CPUCores: 2,
Memory: "2Gi",
}, workloads.WithNamespace("virtwork"))
Expect(err).NotTo(HaveOccurred())
Expect(w.Name()).To(Equal("tps"))
})

It("should create workloads with provided config", func() {
Expand Down Expand Up @@ -157,7 +168,7 @@ var _ = Describe("Registry", func() {
})

var _ = Describe("AllWorkloadNames", func() {
It("should contain all five workload names sorted", func() {
Expect(workloads.AllWorkloadNames).To(Equal([]string{"cpu", "database", "disk", "memory", "network"}))
It("should contain all six workload names sorted", func() {
Expect(workloads.AllWorkloadNames).To(Equal([]string{"cpu", "database", "disk", "memory", "network", "tps"}))
})
})
Loading
Loading