Skip to content

feat(disruption): disk full injection.#1058

Open
Zenithar wants to merge 10 commits into
mainfrom
zenithar/chaos-controller/disk_full_disruption
Open

feat(disruption): disk full injection.#1058
Zenithar wants to merge 10 commits into
mainfrom
zenithar/chaos-controller/disk_full_disruption

Conversation

@Zenithar

@Zenithar Zenithar commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

  • Adds new functionality

Adds a new diskFull disruption kind that genuinely fills a target pod volume using the fallocate(2) syscall, causing real ENOSPC errors on all subsequent write operations. This fills a gap where existing disruptions (DiskPressure = I/O throttling, DiskFailure = eBPF on openat only) don't simulate actual disk exhaustion visible to monitoring and all syscalls.

Features

  • Volume fill via ballast file: Creates a ballast file via fallocate(2) syscall (instant, O(1) on ext4/xfs) to genuinely consume disk space. Falls back to writing zeros on unsupported filesystems.
  • Safety: 1Mi minimum free space floor (overridable via unsafeMode.allowDiskFullNoFloor). Pod-level only. Webhook warning for ephemeral-storage eviction risk.
  • Pure Go fallocate: Vendored fallocate/ package (adapted from detailyang/go-fallocate, MIT) — no dependency on fallocate or dd binaries in the injector image.

How it differs from existing disruptions

Disruption Mechanism ENOSPC on writes? Visible to df/monitoring?
Disk Pressure Cgroup blkio throttling No No
Disk Failure eBPF on openat only Only on file open No
Disk Full (new) Real space allocation Yes (all syscalls) Yes

Example

apiVersion: chaos.datadoghq.com/v1beta1
kind: Disruption
metadata:
  name: disk-full-test
spec:
  selector:
    app: my-service
  count: 1
  level: pod
  duration: 10m
  diskFull:
    path: "/data"
    capacity: "95%"

Code Quality Checklist

  • The documentation is up to date.
  • My code is sufficiently commented and passes continuous integration checks.
  • I have signed my commit (see Contributing Docs).

Testing

  • I leveraged continuous integration testing
    • by adding new unit tests.
  • I manually tested the following steps:
    • locally.
    • as a canary deployment to a cluster.

Test coverage

  • Spec validation: capacity/remaining mutual exclusivity, boundary values, GenerateArgs, Explain
  • Injector: creation, inject with capacity/remaining, dry-run, remaining > available (skip), inject+clean round trip, idempotent cleanup

Files changed (24 files, ~1350 lines)

Component Files
CRD spec + validation api/v1beta1/disk_full.go, disruption_types.go, disruption_webhook.go, safemode.go
Injector injector/disk_full.go (ballast file via fallocate)
CLI cli/injector/disk_full.go, cli/injector/main.go
fallocate package fallocate/ (4 platform-specific files, adapted from go-fallocate MIT)
Safemode safemode/safemode_disk_full.go, safemode/safemode.go
Types types/types.go (DisruptionKindDiskFull)
Docs docs/disk_full.md, docs/disruption_catalogue.md
Tests api/v1beta1/disk_full_test.go, injector/disk_full_test.go

@Zenithar Zenithar force-pushed the zenithar/chaos-controller/disk_full_disruption branch from d238abd to 6109573 Compare April 8, 2026 15:11
@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Apr 8, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 65.95%
Overall Coverage: 46.16% (+0.51%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 9bc6a04 | Docs | Datadog PR Page | Give us feedback!

@Zenithar Zenithar self-assigned this Apr 9, 2026
@Zenithar Zenithar marked this pull request as ready for review April 13, 2026 07:25
@Zenithar Zenithar requested a review from a team as a code owner April 13, 2026 07:25
@aymericDD

Copy link
Copy Markdown
Contributor

The diskFull disruption creates a ballast file on the host filesystem via the injector pod. The injector pod mounts the host root at /mnt/host, but that mount has ReadOnly: true — which was correct for all existing injectors (network, CPU, etc.) that only read the host. diskFull must write to the host, so it gets read-only file system ENOSPC before even starting.

Root cause

services/chaospod.go:573 — the host VolumeMount unconditionally sets ReadOnly: true:

     {
         Name:      "host",
         MountPath: "/mnt/host",
         ReadOnly:  true,   // ← must be false for diskFull
     },

Fix

  1. Add a hostWritable bool parameter to generateChaosPodSpec

File: services/chaospod.go:466

Change signature:

     func (m *chaosPodService) generateChaosPodSpec(..., hostWritable bool) corev1.PodSpec {

Inside the function, use the parameter:

     {
         Name:      "host",
         MountPath: "/mnt/host",
         ReadOnly:  !hostWritable,
     },
  1. Pass kind == DisruptionKindDiskFull from the call site

File: services/chaospod.go:332

     Spec: m.generateChaosPodSpec(
         targetNodeName,
         terminationGracePeriod,
         activeDeadlineSeconds,
         args,
         hostPathDirectory,
         hostPathFile,
         kind == chaostypes.DisruptionKindDiskFull,  // hostWritable
     ),
 **Critical files**

 - `services/chaospod.go` — only file to modify

@Zenithar

Zenithar commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

Many thanks for the deep investigation. I will fix that ASAP. I still have concerns about allowing write to a complete FS for writing a ballast in a dedicated directory. It will allow someone with access to the pod to alter the disrupted pod/node for purposes other than the expected disruption.

I will propose a security gate.

@Zenithar Zenithar marked this pull request as draft April 16, 2026 09:35
@aymericDD

Copy link
Copy Markdown
Contributor

Could you also create an example file to test locally the disruption:

example/disk_full.yaml

# Unless explicitly stated otherwise all files in this repository are licensed
# under the Apache License Version 2.0.
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2026 Datadog, Inc.

apiVersion: chaos.datadoghq.com/v1beta1
kind: Disruption
metadata:
  name: disk-full
  namespace: chaos-demo
spec:
  level: pod
  selector:
    service: demo-curl
  count: 1
  duration: 10m
  diskFull:
    path: "/mnt/data"
    capacity: "95%"

@aymericDD

Copy link
Copy Markdown
Contributor

Could you also update the examples/complete.yaml please

@aymericDD

Copy link
Copy Markdown
Contributor

Could you also update the docs/README.md to add a link to the docs/disk_full.md disruption please

Comment thread api/v1beta1/disruption_types.go Outdated
Comment thread api/v1beta1/disruption_types.go Outdated
Comment thread api/v1beta1/disruption_types.go
Comment thread cli/injector/disk_full.go
Comment thread injector/disk_full.go
@Zenithar Zenithar force-pushed the zenithar/chaos-controller/disk_full_disruption branch from fb46e35 to 6a5b614 Compare June 1, 2026 16:32
@Zenithar Zenithar marked this pull request as ready for review June 3, 2026 14:46
@aymericDD

Copy link
Copy Markdown
Contributor

Full review comments:

  • [P1] Remove the pod-path HostPath mount — /Users/aymeric.daurelle/go/src/github.com/DataDog/chaos-controller/services/chaospod.go:689-695
    When diskFull.path targets a normal pod volume such as an emptyDir/PVC mounted at /data, this value is a container mount path, not a node path. Adding it as a HostPath with type Directory makes kubelet require /data to exist on the node, so the chaos pod can fail before the injector resolves the real /var/lib/kubelet/pods/... backing path.

  • [P2] Fix the rejected 100% example — /Users/aymeric.daurelle/go/src/github.com/DataDog/chaos-controller/docs/disk_full.md:81-81
    This documented manifest is rejected by safetyNetDiskFullMinFreeSpace, because capacity >= 100 returns an error before unsafeMode.allowDiskFullNoFloor is considered. Users following the example cannot create the disruption unless the webhook behavior or the example is changed.

@Zenithar

Zenithar commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Full review comments:

  • [P1] Remove the pod-path HostPath mount — /Users/aymeric.daurelle/go/src/github.com/DataDog/chaos-controller/services/chaospod.go:689-695
    When diskFull.path targets a normal pod volume such as an emptyDir/PVC mounted at /data, this value is a container mount path, not a node path. Adding it as a HostPath with type Directory makes kubelet require /data to exist on the node, so the chaos pod can fail before the injector resolves the real /var/lib/kubelet/pods/... backing path.
  • [P2] Fix the rejected 100% example — /Users/aymeric.daurelle/go/src/github.com/DataDog/chaos-controller/docs/disk_full.md:81-81
    This documented manifest is rejected by safetyNetDiskFullMinFreeSpace, because capacity >= 100 returns an error before unsafeMode.allowDiskFullNoFloor is considered. Users following the example cannot create the disruption unless the webhook behavior or the example is changed.

@aymericDD, it would be interesting to have configuration validation tests. Because most of the configuration files acting as examples are not validated in CI. That would help to spot such discrepancies, especially when we introduce new feature or schema breaking changes.

@Zenithar Zenithar force-pushed the zenithar/chaos-controller/disk_full_disruption branch from 1ad02eb to 02d1b48 Compare June 8, 2026 08:57
@aymericDD

Copy link
Copy Markdown
Contributor

Full review comments:

  • [P1] Remove the pod-path HostPath mount — /Users/aymeric.daurelle/go/src/github.com/DataDog/chaos-controller/services/chaospod.go:689-695
    When diskFull.path targets a normal pod volume such as an emptyDir/PVC mounted at /data, this value is a container mount path, not a node path. Adding it as a HostPath with type Directory makes kubelet require /data to exist on the node, so the chaos pod can fail before the injector resolves the real /var/lib/kubelet/pods/... backing path.
  • [P2] Fix the rejected 100% example — /Users/aymeric.daurelle/go/src/github.com/DataDog/chaos-controller/docs/disk_full.md:81-81
    This documented manifest is rejected by safetyNetDiskFullMinFreeSpace, because capacity >= 100 returns an error before unsafeMode.allowDiskFullNoFloor is considered. Users following the example cannot create the disruption unless the webhook behavior or the example is changed.

@aymericDD, it would be interesting to have configuration validation tests. Because most of the configuration files acting as examples are not validated in CI. That would help to spot such discrepancies, especially when we introduce new feature or schema breaking changes.

@Zenithar Good point, we can create a ticket for that with a design proposal and for now you can rely on e2e-test.

@DataDog DataDog deleted a comment from chatgpt-codex-connector Bot Jun 8, 2026
@aymericDD

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02d1b48f20

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread services/chaospod.go Outdated
@Zenithar Zenithar requested a review from aymericDD June 8, 2026 13:18
Zenithar and others added 8 commits June 8, 2026 15:51
Signed-off-by: Thibault NORMAND <thibault.normand@datadoghq.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Thibault NORMAND <me@zenithar.org>
… address PR comments.

Add diskFull to 5 missing registration points in validateGlobalDisruptionScope
(at-least-one-kind check, ContainerFailure/NodeFailure/PodReplacement
compatibility, OnInit compatibility), DisruptionCount(), and Explain().

Add writable shadow mount for the target path in chaos pod spec so the
injector can write ballast files while keeping /mnt/host read-only.

Add capacity mode test coverage, disk_full example, complete.yaml entry,
and docs/README.md link.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove the secondary `disk-full-target` HostPath volume and its container mount
- Drop unused `path/filepath` import that was only needed for the removed mount
- Simplify the comment to reflect that a single kubelet-pods mount suffices

Rationale: The `disk-full-target` volume was a secondary mount added as a fallback
for hostPath-backed volumes where the host path equals the spec path. Since the
injector already resolves the actual backing path via Runtime().HostPath() at
runtime (covering emptyDir, PVC, ConfigMap, and Secret volumes through the
kubelet-pods mount), the secondary mount is redundant and adds unnecessary
complexity to the chaos pod spec.

This commit made by [/dd:git:commit:atomic](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/atomic.md)
@Zenithar Zenithar force-pushed the zenithar/chaos-controller/disk_full_disruption branch from 4d1c593 to d21716b Compare June 8, 2026 13:52
HostPath signature changed in main to require context.Context as first
argument. Mirrors the pattern used in disk_pressure.go.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@aymericDD

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13817d703a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread injector/disk_full.go Outdated
Comment thread services/chaospod.go Outdated
Comment thread examples/disk_full.yaml
@aymericDD

Copy link
Copy Markdown
Contributor

Bug: diskFull fails on k3s with "read-only file system"

When targeting a PVC-backed volume on a k3s cluster, the injector fails with:

error creating ballast file /mnt/host/var/lib/rancher/k3s/storage/pvc-.../...: read-only file system

Root cause: The chaos pod adds a writable override mount for /var/lib/kubelet/pods (punching through the read-only /mnt/host), but on k3s the local-path-provisioner stores PVCs at /var/lib/rancher/k3s/storage/ — which falls under the read-only parent mount.

Proposed fix: Rather than hardcoding the kubelet path, resolve the writable mount path dynamically at chaos pod creation time using the Kubernetes API:

  1. The controller already fetches the target pod object — find which volume is mounted at spec.diskFull.path
  2. If it's a PVC → fetch the PersistentVolumeClaim → bound PersistentVolume → read pv.Spec.HostPath.Path or pv.Spec.Local.Path → use filepath.Dir() as the writable mount
  3. emptyDir / ConfigMap / Secret → always fall back to /var/lib/kubelet/pods (kubelet-managed, distro-agnostic)

This works for any Kubernetes distribution without configuration or runtime detection heuristics, since the PV host path is always available from the API.

@Zenithar

Copy link
Copy Markdown
Contributor Author

Bug: diskFull fails on k3s with "read-only file system"

When targeting a PVC-backed volume on a k3s cluster, the injector fails with:

error creating ballast file /mnt/host/var/lib/rancher/k3s/storage/pvc-.../...: read-only file system

Root cause: The chaos pod adds a writable override mount for /var/lib/kubelet/pods (punching through the read-only /mnt/host), but on k3s the local-path-provisioner stores PVCs at /var/lib/rancher/k3s/storage/ — which falls under the read-only parent mount.

Proposed fix: Rather than hardcoding the kubelet path, resolve the writable mount path dynamically at chaos pod creation time using the Kubernetes API:

  1. The controller already fetches the target pod object — find which volume is mounted at spec.diskFull.path
  2. If it's a PVC → fetch the PersistentVolumeClaim → bound PersistentVolume → read pv.Spec.HostPath.Path or pv.Spec.Local.Path → use filepath.Dir() as the writable mount
  3. emptyDir / ConfigMap / Secret → always fall back to /var/lib/kubelet/pods (kubelet-managed, distro-agnostic)

This works for any Kubernetes distribution without configuration or runtime detection heuristics, since the PV host path is always available from the API.

@aymericDD I don't consider this an error. Trying to create a file on a read-only volume is a configuration error, not a code error.

For simplicity, I recommend using emptyDir so you don't have to handle the path for pod-level disruption. Concerning node-level disruption, I'm torn between removing the base path enforcement, allowing the user to run the disruption in any location, and limiting the user to specific paths; both have pros/cons.

@Zenithar

Copy link
Copy Markdown
Contributor Author

Disk-Full Disruption Behaviour Assessment

Date: 2026-06-11
Cluster: Lima k3s single-node (lima-thibault-normand, k3s v1.26.3+k3s1)
Operator version: zenithar/chaos-controller/disk_full_disruption (commit 13817d7 + fixes from this session)
Assessed by: Thibault NORMAND


1. Expectations (established before cluster interaction)

ID Description Code Location Level Status
EX-001 Disruption targets only pods with the matching label selector controllers/disruption_controller.go:994 pod PASS
EX-002 Controller resolves PVC container path → PV HostPath via Kubernetes API services/chaospod.go:711-733 pod PASS
EX-003 Chaos pod disk-full-writable volume mounts the PV host path writable services/chaospod.go:683-701 pod PASS
EX-004 Injector resolves /mnt/data to the k3s backing path via Runtime().HostPath() injector/disk_full.go:65 pod PASS
EX-005 Ballast file created at hostPath/.chaos-diskfull-<ns>-<name>-<target> injector/disk_full.go:83 pod PASS
EX-006 capacity: "50%" fills disk to 50% of total volume size injector/disk_full.go:204-224 pod PASS
EX-007 remaining: "80Gi" leaves exactly 80Gi free (fills the difference) injector/disk_full.go:227-238 pod PASS
EX-008 Containers without the target path receive a warn log and are skipped injector/disk_full.go:70-73 pod PASS
EX-009 1 MiB safety floor clamps fill on second pulse (volume already near target) injector/disk_full.go:127-144 pod PASS
EX-010 Clean() removes the ballast file; disk returns to baseline injector/disk_full.go:248-263 pod PASS
EX-011 Clean() is idempotent (no error if file already removed) injector/disk_full.go:252-254 pod PASS
EX-012 Webhook rejects level: node with disk full disruptions can only be applied at the pod level api/v1beta1/disruption_types.go:816-817 node PASS
EX-013 Webhook rejects containers: field with disk-full api/v1beta1/disruption_types.go:762-763 pod PASS
EX-014 Webhook rejects path: / api/v1beta1/disk_full.go:38-39 pod PASS
EX-015 Webhook rejects both capacity and remaining set simultaneously api/v1beta1/disk_full.go:49-55 pod PASS
EX-016 capacity: "100%" is accepted when safemode is disabled (no floor check) api/v1beta1/disruption_webhook.go:275 (safemode gate) pod PASS

2. Pod-level disruption — PVC-backed volume (k3s local-path)

2.1 Test: capacity: "50%" on demo-curl PVC

Target: demo-curl-547d5bc489-r25pc (PVC demo backed by k3s HostPath PV)
PV host path: /var/lib/rancher/k3s/storage/pvc-8bd0e3c3-d279-4ade-8552-a418be70a2da_chaos-demo_demo

Manifest:

apiVersion: chaos.datadoghq.com/v1beta1
kind: Disruption
metadata:
  name: disk-full-pvc
  namespace: chaos-demo
spec:
  level: pod
  selector:
    service: demo-curl
  count: 1
  duration: 5m
  diskFull:
    path: /mnt/data
    capacity: "50%"

Disk state:

Phase Used Avail Use%
Baseline 4.2G 92G 5%
After injection 48G 48G 50%
After cleanup 4.3G 92G 5%

Chaos pod volume spec (key section):

{
  "name": "disk-full-writable",
  "hostPath": {
    "path": "/var/lib/rancher/k3s/storage/pvc-8bd0e3c3-d279-4ade-8552-a418be70a2da_chaos-demo_demo",
    "type": "DirectoryOrCreate"
  }
}

Mounted at: /mnt/host/var/lib/rancher/k3s/storage/pvc-8bd0e3c3-d279-4ade-8552-a418be70a2da_chaos-demo_demo

Key injector log entries:

{"level":"info","message":"injecting disk full disruption",
  "path":"/mnt/host/var/lib/rancher/k3s/storage/pvc-8bd0e3c3-d279-4ade-8552-a418be70a2da_chaos-demo_demo",
  "ballastPath":"/mnt/host/.../  .chaos-diskfull-chaos-demo-disk-full-pvc-demo-curl-547d5bc489-r25pc",
  "bytesToFill":46872190976,"totalBytes":102888095744,"availableBytes":98316238848}
{"level":"info","message":"disk full disruption injected successfully"}

Observations:

  • Dynamic PV resolution via resolveDiskFullWritablePath correctly identified k3s storage path.
  • Three containers without /mnt/data mount received warn + skip, not errors.
  • Second injection pulse logged "volume already at or past target fill level, skipping injection" — 1 MiB floor working correctly.
  • Cleanup instant: chaos pod terminated, ballast file removed, disk back to 5%.

2.2 Test: remaining: "80Gi" on demo-storage-0 (StatefulSet with shared PVC)

Target: demo-storage-0 (PVC shared-storage-demo-storage-0 backed by k3s HostPath PV)
PV host path: /var/lib/rancher/k3s/storage/pvc-721998ce-0747-4683-a47c-f024a4a31371_chaos-demo_shared-storage-demo-storage-0

Manifest:

apiVersion: chaos.datadoghq.com/v1beta1
kind: Disruption
metadata:
  name: disk-full-remaining
  namespace: chaos-demo
spec:
  level: pod
  selector:
    service: demo-storage
  count: 1
  duration: 5m
  diskFull:
    path: /mnt/shared
    remaining: "80Gi"

Disk state:

Phase Used Avail Use%
Baseline 4.1G 91.7G 4%
After injection 15.8G 80.0G 16%
After cleanup 3.8G 92.0G 4%

Observations:

  • remaining: "80Gi" filled exactly 80Gi target (12.0 GiB ballast file, bytesToFill=12879540224).
  • Ballast filename: .chaos-diskfull-chaos-demo-disk-full-remaining-demo-storage-0 — per-target suffix prevents collision between StatefulSet replicas.
  • Two containers in demo-storage-0 share the same /mnt/shared volume — both containers would see the same fill effect (correct by design, documented in webhook rejection of containers: field).

3. Webhook validation

Guard Input Expected rejection Actual rejection Status
Node level level: node "disk full disruptions can only be applied at the pod level" * Spec: disk full disruptions can only be applied at the pod level PASS
Containers specified containers: [read-file] container isolation error * Spec: disk full disruptions apply to the entire volume, specifying certain containers does not isolate the disruption PASS
Path / path: / path disallowed * Spec: path '/' is not allowed for disk full disruptions; it would fill the node's shared container storage PASS
Mutually exclusive capacity: "50%" + remaining: "1Gi" exactly one required * Spec: capacity and remaining are mutually exclusive, only one can be set PASS
capacity: "100%" No unsafeMode rejected by 1 MiB floor check ACCEPTED (safemode disabled on this cluster) FINDING-01

4. Code flow summary

Disruption CR created
  └── ValidateCreate webhook
        ├── Spec.Validate() — path, capacity%, remaining quantity, mutual exclusion
        ├── level != node check — api/v1beta1/disruption_types.go:816
        └── containers == [] check — api/v1beta1/disruption_types.go:762
                (safemode guards for floor — only if enableSafemode=true)

Reconcile loop
  └── GenerateChaosPodsOfDisruption
        └── GenerateChaosPodOfDisruption
              └── resolveDiskFullWritablePath(disruption, targetName)
                    ├── level == node → return spec.Path directly
                    └── level == pod → resolvePodVolumeHostPath (Reader, 10s timeout)
                          ├── Get Pod from API (uncached Reader)
                          ├── diskFullVolumeAtPath — deepest prefix match
                          ├── PVC volume → resolvePVCHostPath
                          │     ├── Get PVC (Reader)
                          │     ├── Get PV (Reader)
                          │     └── return HostPath.Path or Local.Path
                          └── emptyDir/ConfigMap/Secret → /var/lib/kubelet/pods
              └── generateChaosPodSpec — mounts resolved path writable as disk-full-writable

Chaos pod (injector) runs
  └── NewDiskFullInjector
        └── Runtime().HostPath(ctx, containerID, spec.Path) — 30s timeout
              → resolves container path to host path via container runtime
  └── injectVolumeFill
        ├── syscall.Statfs — get totalBytes, availableBytes
        ├── computeBytesToFill (capacity% or remaining quantity)
        ├── 1 MiB floor check (skip/clamp if AllowNoFloor=false)
        ├── dry-run guard
        └── fallocate.Fallocate — Linux: fallocate(2), Darwin: write zeros
  └── Clean()
        └── os.Remove(ballastPath) — idempotent on ErrNotExist

Relevant files

File Role
api/v1beta1/disk_full.go DiskFullSpec, Validate(), GenerateArgs()
api/v1beta1/disruption_types.go:762,816 Level and containers-field restrictions
api/v1beta1/disruption_webhook.go:762-784 safetyNetDiskFullMinFreeSpace (safemode-gated)
injector/disk_full.go NewDiskFullInjector, Inject, Clean
services/chaospod.go:707-808 Dynamic PV host path resolution
controllers/disruption_controller.go:14-15 RBAC markers for PVC/PV read access
fallocate/ Linux fallocate(2) / Darwin zero-write

5. Findings

ID Description Severity Status
FINDING-01 capacity: "100%" is accepted when enableSafemode=false (default in dev clusters). The 1 MiB floor check in safetyNetDiskFullMinFreeSpace is gated behind if enableSafemode. A production operator with safemode disabled can accidentally schedule a disk-filling disruption with no floor, leaving 0 bytes. Warn Known design — operators should enable safemode in prod
FINDING-02 If the k8s node goes into DiskPressure during an active disk-full disruption (e.g., 50% capacity test on a small disk), new chaos pods for subsequent disruptions are evicted by the kubelet before they can inject. The node needs ~10s after cleanup to clear DiskPressure. Info Expected kubelet behaviour; use conservative capacity values
FINDING-03 Containers without the targeted path receive warn: could not apply injector on container — correct skip behaviour, but callers observing injectedTargetsCount may see PartiallyInjected until the matching container injects. Info Expected; documented in skip condition EX-008
FINDING-04 resolveDiskFullWritablePath uses context.Background() (not the reconcile ctx). If the reconcile is cancelled mid-flight, the PV resolution continues for up to 10s. Info Low-impact; background context with 10s ceiling

6. Infrastructure changes made during assessment

Change File Reason
Added persistentvolumeclaims and persistentvolumes RBAC controllers/disruption_controller.go:14-15 Controller lacked read access to PVCs/PVs needed for dynamic path resolution
Switched resolvePodVolumeHostPath and resolvePVCHostPath to use m.config.Reader services/chaospod.go:739,769,778 The cache-backed Client has no informers for PVC/PV types; Reader bypasses the cache and calls the API directly

7. Test artifacts

File Description
examples/disk_full.yaml Existing example (updated selector to service: demo-curl for this cluster)
/tmp/disk_full_assess_pod_pvc.yaml Pod-level, capacity 50%, service: demo-curl — main happy-path test
/tmp/disk_full_assess_remaining.yaml Pod-level, remaining 80Gi, service: demo-storage — remaining-mode and StatefulSet test

@Zenithar Zenithar force-pushed the zenithar/chaos-controller/disk_full_disruption branch from 2d6508d to 9bc6a04 Compare June 11, 2026 16:22
@Zenithar Zenithar requested a review from aymericDD June 11, 2026 16:23
@aymericDD

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9bc6a042f6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread services/chaospod.go

for _, container := range pod.Spec.Containers {
for _, mount := range container.VolumeMounts {
if strings.HasPrefix(containerPath, mount.MountPath) && len(mount.MountPath) > bestLen {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match volume mounts on path boundaries

When the requested disk-full path only shares a string prefix with another mount (for example a pod has /data mounted and the disruption uses /database or /data2), this selects the /data volume as the writable host path even though it is not an ancestor of the target path. That makes the chaos pod mount the wrong backing volume writable and can cause the injector to skip or fail against the actual target path; require an exact match or a / boundary after the mount path.

Useful? React with 👍 / 👎.

@aymericDD aymericDD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stamp it to unblock you

case types.DisruptionKindDiskFull:
if b.Spec.DiskFull == nil {
b.Spec.DiskFull = &v1beta1.DiskFullSpec{
Path: "/data",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you defining default value for the disruption?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants