diff --git a/build/golden-image/Containerfile b/build/golden-image/Containerfile index f112004..c99ab1f 100644 --- a/build/golden-image/Containerfile +++ b/build/golden-image/Containerfile @@ -21,6 +21,7 @@ RUN dnf install -y \ postgresql-server \ iproute-tc \ iptables-nft \ + kernel-modules-extra \ netperf \ python3 \ && dnf clean all \ diff --git a/build/golden-image/README.md b/build/golden-image/README.md index 7685fa3..604c0a9 100644 --- a/build/golden-image/README.md +++ b/build/golden-image/README.md @@ -15,7 +15,8 @@ The golden image is based on `quay.io/containerdisks/fedora:41` and includes all - **python3** — HTTP file server for the tps workload's application-layer transfers - **postgresql-server** — PostgreSQL database with pgbench (database workload) - **procps-ng** — `ps`, `pkill`, `kill` for chaos-process -- **iproute-tc** — Traffic control (`tc`) and the `sch_netem` kernel module hooks used by the chaos-network workload to inject latency and packet loss +- **iproute-tc** — Traffic control (`tc`) used by the chaos-network workload to inject latency and packet loss +- **kernel-modules-extra** — Provides the `sch_netem` kernel module required by chaos-network; pre-installing means the cloud-init `dnf install` completes instantly (package already present) instead of fetching over the network - **iptables-nft** — Firewall rules; reserved for future network partition / blackhole scenarios Additional tools like `fallocate` and `dd` (used by chaos-disk) are already present in the base Fedora image. diff --git a/docs/chaos-workloads.md b/docs/chaos-workloads.md index 3969bc7..f180703 100644 --- a/docs/chaos-workloads.md +++ b/docs/chaos-workloads.md @@ -133,7 +133,7 @@ workloads: ### Dependencies -The workload installs `iproute-tc` and loads the `sch_netem` kernel module on start. The base Fedora container disk has both available; the golden image (`build/golden-image/`) pre-installs `iproute-tc` to avoid the first-boot package install. `sch_netem` is loaded from the `kernel-modules-extra` package, which the cloud-init runs `dnf install` for on first boot. +The workload requires `iproute-tc` and the `sch_netem` kernel module. The golden image (`build/golden-image/`) pre-installs both `iproute-tc` and `kernel-modules-extra` (which provides `sch_netem`), so the cloud-init `dnf install` completes instantly (package already present) and no network fetch is needed. On the default Fedora container disk, cloud-init installs `iproute-tc` via `packages` and `kernel-modules-extra-$(uname -r)` via `runcmd`, which ensures the module version matches the running kernel. The start script runs `modprobe sch_netem` to load the module before applying the qdisc. ### Example diff --git a/internal/workloads/chaos_network.go b/internal/workloads/chaos_network.go index 84cd046..19a273e 100644 --- a/internal/workloads/chaos_network.go +++ b/internal/workloads/chaos_network.go @@ -92,7 +92,8 @@ func (w *ChaosNetworkWorkload) Name() string { } // CloudInitUserdata returns cloud-init YAML that configures tc/netem for network chaos -// injection via systemd. Assumes iproute-tc is pre-installed (golden image dependency). +// injection via systemd. The golden image pre-installs iproute-tc and kernel-modules-extra +// (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(), diff --git a/internal/workloads/chaos_network_test.go b/internal/workloads/chaos_network_test.go index ea7242f..506c10a 100644 --- a/internal/workloads/chaos_network_test.go +++ b/internal/workloads/chaos_network_test.go @@ -4,6 +4,8 @@ package workloads_test import ( + "strings" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -72,32 +74,30 @@ var _ = Describe("ChaosNetworkWorkload", func() { Expect(parsed).To(HaveKey("runcmd")) runcmds := parsed["runcmd"].([]interface{}) - foundKernelModules := false + foundDnf := false + foundDaemonReload := false foundEnable := false - kernelModulesIdx := -1 - enableIdx := -1 - for i, cmd := range runcmds { + for _, cmd := range runcmds { cmdSlice := cmd.([]interface{}) - if len(cmdSlice) >= 3 { - if cmdSlice[0] == "bash" && cmdSlice[1] == "-c" { - cmdStr := cmdSlice[2].(string) - if cmdStr == "dnf install -y kernel-modules-extra-$(uname -r)" { - foundKernelModules = true - kernelModulesIdx = i - } - } - if cmdSlice[0] == "systemctl" && cmdSlice[1] == "enable" { - foundEnable = true - enableIdx = i - Expect(cmdSlice[2]).To(Equal("--now")) + if len(cmdSlice) >= 3 && cmdSlice[0] == "bash" && cmdSlice[1] == "-c" { + script, _ := cmdSlice[2].(string) + if strings.Contains(script, "kernel-modules-extra") { + foundDnf = true + Expect(script).To(ContainSubstring("$(uname -r)"), + "should install kernel-modules-extra matching the running kernel") } } + if len(cmdSlice) >= 2 && cmdSlice[0] == "systemctl" && cmdSlice[1] == "daemon-reload" { + foundDaemonReload = true + } + if len(cmdSlice) >= 3 && cmdSlice[0] == "systemctl" && cmdSlice[1] == "enable" { + foundEnable = true + Expect(cmdSlice[2]).To(Equal("--now")) + } } - Expect(foundKernelModules).To(BeTrue(), "should install kernel-modules-extra for running kernel") - Expect(foundEnable).To(BeTrue(), "should have systemctl enable command") - Expect(kernelModulesIdx).To( - BeNumerically("<", enableIdx), - "kernel modules install should come before service enable") + Expect(foundDnf).To(BeTrue(), "should have dnf install kernel-modules-extra") + Expect(foundDaemonReload).To(BeTrue(), "should have systemctl daemon-reload") + Expect(foundEnable).To(BeTrue(), "should have systemctl enable --now") }) It("should produce valid YAML", func() {