mirror of
https://github.com/krkn-chaos/krkn.git
synced 2026-08-25 09:27:36 +00:00
feat: storage I/O throttle scenario (cgroups v1/v2) for PVC-backed workloads (#1296)
* feat(storage-throttle): add storage throttle scenario and tests Consolidate the storage-throttle implementation, scenario configs, CI v2 coverage, and krkn-lib 6.0.10 dependency update into a single signed commit for cleaner PR history. Signed-off-by: ddjain <darjain@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: ddjain <darjain@redhat.com> * adding need dco auto review (#1329) Signed-off-by: Paige Patton <prubenda@redhat.com> Signed-off-by: ddjain <darjain@redhat.com> * fix: start_klusterlet_scenario action calls start instead of stop (#1324) The start_klusterlet_scenario branch in inject_managedcluster_scenario was calling stop_klusterlet_scenario on the scenarios object instead of start_klusterlet_scenario. Any user configuring this action would stop the klusterlet (scale to 0) instead of starting it (scale to 3). Fixes #1323 Signed-off-by: v0idheaven <dahiyavarun2007@gmail.com> Co-authored-by: Paige Patton <64206430+paigerube14@users.noreply.github.com> Signed-off-by: ddjain <darjain@redhat.com> * adding links to issue of completed roadmap items (#1328) Signed-off-by: Paige Patton <prubenda@redhat.com> Signed-off-by: ddjain <darjain@redhat.com> * container scenario template image update (#1342) Signed-off-by: Paige Patton <prubenda@redhat.com> Signed-off-by: ddjain <darjain@redhat.com> --------- Signed-off-by: ddjain <darjain@redhat.com> Signed-off-by: Paige Patton <prubenda@redhat.com> Signed-off-by: v0idheaven <dahiyavarun2007@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Paige Patton <64206430+paigerube14@users.noreply.github.com> Co-authored-by: Varun Yadav <dahiyavarun2007@gmail.com>
This commit is contained in:
co-authored by
Paige Patton
Cursor
Varun Yadav
parent
3391ff2453
commit
eb2efa84ea
@@ -9,6 +9,7 @@ markers =
|
||||
functional: marks a test as a functional test (deselect with '-m "not functional"')
|
||||
pod_disruption: marks a test as a pod disruption scenario test
|
||||
application_outage: marks a test as an application outage scenario test
|
||||
storage_throttle: marks a test as a storage throttle scenario test
|
||||
no_workload: skip workload deployment for this test (e.g. negative tests)
|
||||
order: set test order (pytest-order)
|
||||
junit_family = xunit2
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# PVC + Deployment for storage throttle integration test.
|
||||
# Namespace is patched at deploy time by the test framework.
|
||||
# Uses the default StorageClass (e.g. local-path on KinD).
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: krkn-throttle-pvc
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 128Mi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: krkn-throttle-target
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: krkn-throttle-target
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: krkn-throttle-target
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: nginx:alpine
|
||||
command: ["/bin/sh", "-c", "trap : TERM INT; sleep infinity & wait"]
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: krkn-throttle-pvc
|
||||
@@ -0,0 +1,13 @@
|
||||
# Base storage_throttle scenario. Tests load this and patch namespace, pvc_name, etc.
|
||||
storage_throttle_scenario:
|
||||
pvc_name: krkn-throttle-pvc
|
||||
pod_name: ""
|
||||
namespace: default
|
||||
mount_path: /data
|
||||
throttle_type: bandwidth
|
||||
read_bps: 1Mi
|
||||
write_bps: 512Ki
|
||||
read_iops: 100
|
||||
write_iops: 50
|
||||
duration: 15
|
||||
image: quay.io/krkn-chaos/krkn:tools
|
||||
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
Functional test for storage throttle scenario (cgroup I/O throttle on PVC-backed volume).
|
||||
|
||||
Deploys a PVC + Deployment in an ephemeral namespace, runs the storage_throttle
|
||||
scenario, and verifies:
|
||||
- Krkn exits 0 (throttle applied and removed cleanly)
|
||||
- Target pods survive (running and ready after scenario)
|
||||
- Negative cases: bad namespace and invalid throttle_type fail gracefully
|
||||
|
||||
Follows the CI/tests_v2 BaseScenarioTest pattern.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from lib.base import BaseScenarioTest, READINESS_TIMEOUT
|
||||
from lib.utils import (
|
||||
assert_all_pods_running_and_ready,
|
||||
assert_kraken_failure,
|
||||
assert_kraken_success,
|
||||
assert_pod_count_unchanged,
|
||||
get_pods_list,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.functional
|
||||
@pytest.mark.storage_throttle
|
||||
class TestStorageThrottle(BaseScenarioTest):
|
||||
"""Storage throttle scenario: apply I/O cgroup limits on a PVC mount and verify recovery."""
|
||||
|
||||
WORKLOAD_MANIFEST = "CI/tests_v2/scenarios/storage_throttle/resource.yaml"
|
||||
WORKLOAD_IS_PATH = True
|
||||
LABEL_SELECTOR = "app=krkn-throttle-target"
|
||||
SCENARIO_NAME = "storage_throttle"
|
||||
SCENARIO_TYPE = "storage_throttle_scenarios"
|
||||
NAMESPACE_KEY_PATH = ["storage_throttle_scenario", "namespace"]
|
||||
NAMESPACE_IS_REGEX = False
|
||||
OVERRIDES_KEY_PATH = ["storage_throttle_scenario"]
|
||||
|
||||
@pytest.mark.order(1)
|
||||
def test_bandwidth_throttle_and_recovery(self, wait_for_pods_running):
|
||||
"""Bandwidth throttle: apply read/write bps limits, verify Krkn success and pod recovery."""
|
||||
ns = self.ns
|
||||
before = get_pods_list(self.k8s_core, ns, self.LABEL_SELECTOR)
|
||||
|
||||
result = self.run_scenario(self.tmp_path, ns, overrides={
|
||||
"throttle_type": "bandwidth",
|
||||
"read_bps": "1Mi",
|
||||
"write_bps": "512Ki",
|
||||
"duration": 15,
|
||||
})
|
||||
assert_kraken_success(result, context=f"bandwidth namespace={ns}", tmp_path=self.tmp_path)
|
||||
|
||||
wait_for_pods_running(ns, self.LABEL_SELECTOR, timeout=READINESS_TIMEOUT)
|
||||
after = get_pods_list(self.k8s_core, ns, self.LABEL_SELECTOR)
|
||||
assert_pod_count_unchanged(before, after, namespace=ns)
|
||||
assert_all_pods_running_and_ready(after, namespace=ns)
|
||||
|
||||
@pytest.mark.order(2)
|
||||
def test_iops_throttle_and_recovery(self, wait_for_pods_running):
|
||||
"""IOPS throttle: apply read/write iops limits, verify Krkn success and pod recovery."""
|
||||
ns = self.ns
|
||||
before = get_pods_list(self.k8s_core, ns, self.LABEL_SELECTOR)
|
||||
|
||||
result = self.run_scenario(
|
||||
self.tmp_path, ns,
|
||||
overrides={
|
||||
"throttle_type": "iops",
|
||||
"read_iops": 50,
|
||||
"write_iops": 25,
|
||||
"duration": 15,
|
||||
},
|
||||
config_filename="test_iops_config.yaml",
|
||||
)
|
||||
assert_kraken_success(result, context=f"iops namespace={ns}", tmp_path=self.tmp_path)
|
||||
|
||||
wait_for_pods_running(ns, self.LABEL_SELECTOR, timeout=READINESS_TIMEOUT)
|
||||
after = get_pods_list(self.k8s_core, ns, self.LABEL_SELECTOR)
|
||||
assert_pod_count_unchanged(before, after, namespace=ns)
|
||||
assert_all_pods_running_and_ready(after, namespace=ns)
|
||||
|
||||
@pytest.mark.order(3)
|
||||
def test_both_throttle_and_recovery(self, wait_for_pods_running):
|
||||
"""Combined throttle: apply both bps and iops limits, verify Krkn success and pod recovery."""
|
||||
ns = self.ns
|
||||
before = get_pods_list(self.k8s_core, ns, self.LABEL_SELECTOR)
|
||||
|
||||
result = self.run_scenario(
|
||||
self.tmp_path, ns,
|
||||
overrides={
|
||||
"throttle_type": "both",
|
||||
"read_bps": "1Mi",
|
||||
"write_bps": "512Ki",
|
||||
"read_iops": 50,
|
||||
"write_iops": 25,
|
||||
"duration": 15,
|
||||
},
|
||||
config_filename="test_both_config.yaml",
|
||||
)
|
||||
assert_kraken_success(result, context=f"both namespace={ns}", tmp_path=self.tmp_path)
|
||||
|
||||
wait_for_pods_running(ns, self.LABEL_SELECTOR, timeout=READINESS_TIMEOUT)
|
||||
after = get_pods_list(self.k8s_core, ns, self.LABEL_SELECTOR)
|
||||
assert_pod_count_unchanged(before, after, namespace=ns)
|
||||
assert_all_pods_running_and_ready(after, namespace=ns)
|
||||
|
||||
@pytest.mark.no_workload
|
||||
def test_bad_namespace_fails(self):
|
||||
"""Scenario targeting non-existent namespace causes Krkn to exit non-zero."""
|
||||
scenario = self.load_and_patch_scenario(
|
||||
self.repo_root, "nonexistent-namespace-xyz-99999",
|
||||
pod_name="nonexistent-pod",
|
||||
)
|
||||
scenario_path = self.write_scenario(self.tmp_path, scenario, suffix="_bad_ns")
|
||||
config_path = self.build_config(
|
||||
self.SCENARIO_TYPE, str(scenario_path),
|
||||
filename="storage_throttle_bad_ns_config.yaml",
|
||||
)
|
||||
result = self.run_kraken(config_path)
|
||||
assert_kraken_failure(
|
||||
result, context=f"bad namespace test", tmp_path=self.tmp_path,
|
||||
)
|
||||
|
||||
@pytest.mark.no_workload
|
||||
def test_invalid_throttle_type_fails(self):
|
||||
"""Invalid throttle_type causes Krkn to exit non-zero."""
|
||||
scenario = self.load_and_patch_scenario(
|
||||
self.repo_root, self.ns,
|
||||
throttle_type="invalid_type",
|
||||
pod_name="doesnt-matter",
|
||||
)
|
||||
scenario_path = self.write_scenario(self.tmp_path, scenario, suffix="_bad_type")
|
||||
config_path = self.build_config(
|
||||
self.SCENARIO_TYPE, str(scenario_path),
|
||||
filename="storage_throttle_bad_type_config.yaml",
|
||||
)
|
||||
result = self.run_kraken(config_path)
|
||||
assert_kraken_failure(
|
||||
result, context=f"invalid throttle_type test", tmp_path=self.tmp_path,
|
||||
)
|
||||
Reference in New Issue
Block a user