Skip to content

feat: add HTTP load testing scenario plugin#1144

Open
AR21SM wants to merge 4 commits intokrkn-chaos:mainfrom
AR21SM:feature/http-load-scenario
Open

feat: add HTTP load testing scenario plugin#1144
AR21SM wants to merge 4 commits intokrkn-chaos:mainfrom
AR21SM:feature/http-load-scenario

Conversation

@AR21SM
Copy link
Contributor

@AR21SM AR21SM commented Feb 1, 2026

User description

Type of change

  • Refactor
  • New feature
  • Bug fix
  • Optimization

Description

Implements HTTP load testing scenario plugin to simulate heavy HTTP load on Kubernetes Services, Ingress, or OpenShift Routes. Uses the hey HTTP load generator (chosen over Gatling/Goku for its lightweight single-binary design, existing Docker image, and simpler CLI-based configuration suitable for K8s Jobs).

Features:

  • Target Kubernetes Services (with cluster DNS)
  • Target Ingress resources (with TLS detection)
  • Target OpenShift Routes (with TLS detection)
  • Direct URL targeting
  • Configurable concurrency, duration, RPS limit
  • Custom HTTP headers, request body, content-type
  • Metrics collection from hey output
  • Multiple load generator pods support

Related Tickets & Documents

Documentation

  • Is documentation needed for this update?

Related Documentation PR (if applicable)

Checklist before requesting a review

  • Ensure the changes and proposed solution have been discussed in the relevant issue and have received acknowledgment from the community or maintainers.
  • I have performed a self-review of my code by running krkn and specific scenario
  • If it is a core feature, I have added thorough unit tests with above 80% coverage

REQUIRED:
Description of combination of tests performed and output of run

python -m unittest tests.test_http_load_scenario_plugin -v

test_build_load_command_basic (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_build_load_command_with_body_and_headers (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_build_load_command_with_content_type_only (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_build_load_command_with_rps (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_get_scenario_types (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_resolve_ingress_url (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_resolve_ingress_url_with_tls (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_resolve_route_url (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_resolve_route_url_no_tls (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_resolve_target_url_direct (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_resolve_target_url_service (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_resolve_target_url_service_not_found (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_run_create_job_failure (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_run_missing_target (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_run_service_not_found (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_run_success_with_service (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_run_with_ingress_target (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok
test_run_with_route_target (tests.test_http_load_scenario_plugin.TestHttpLoadScenarioPlugin) ... ok

----------------------------------------------------------------------
Ran 18 tests in 15.048s

OK

PR Type

Enhancement


Description

  • Implements HTTP load testing scenario plugin using hey load generator

  • Supports multiple target types: Services, Ingress, Routes, direct URLs

  • Configurable concurrency, duration, RPS limit, and HTTP parameters

  • Automatic TLS detection for Ingress and OpenShift Routes

  • Comprehensive test coverage with 18 unit tests (80%+ coverage)


Diagram Walkthrough

flowchart LR
  Config["Load Test Config<br/>YAML"] -->|Parse| Plugin["HttpLoadScenarioPlugin"]
  Plugin -->|Resolve| Target["Target URL<br/>Service/Ingress/Route"]
  Plugin -->|Build| Command["hey Load Command"]
  Command -->|Create| Jobs["K8s Jobs<br/>Multiple Pods"]
  Jobs -->|Execute| Load["HTTP Load Test"]
  Load -->|Collect| Metrics["Metrics & Logs"]
  Metrics -->|Report| Status["Kraken Status"]
Loading

File Walkthrough

Relevant files
Enhancement
http_load_scenario_plugin.py
HTTP load testing plugin implementation                                   

krkn/scenario_plugins/http_load/http_load_scenario_plugin.py

  • Main plugin implementation with 331 lines of code
  • Supports targeting Kubernetes Services, Ingress, OpenShift Routes, and
    direct URLs
  • Automatic TLS detection for Ingress and Routes
  • Builds hey load test commands with configurable parameters
    (concurrency, duration, RPS, headers, body)
  • Creates and manages multiple Kubernetes Jobs for distributed load
    testing
  • Collects metrics and logs from completed jobs
  • Includes error handling and cleanup logic
+331/-0 
Tests
test_http_load_scenario_plugin.py
Unit tests for HTTP load scenario plugin                                 

tests/test_http_load_scenario_plugin.py

  • Comprehensive unit test suite with 18 test cases
  • Tests URL resolution for all target types (Service, Ingress, Route,
    direct URL)
  • Tests load command building with various parameter combinations
  • Tests job creation, execution, and cleanup workflows
  • Tests error handling for missing services and failed jobs
  • Achieves 80%+ code coverage with mocked Kubernetes API calls
+503/-0 
Configuration changes
job.j2
Kubernetes Job template for load testing                                 

krkn/scenario_plugins/http_load/job.j2

  • Jinja2 template for Kubernetes Job manifest generation
  • Configures container with hey image and load test command
  • Sets resource requests/limits (64Mi-256Mi memory, 100m-500m CPU)
  • Applies labels for tracking and identification
  • Disables restart policy and sets backoff limit to 0
+30/-0   
Documentation
http_load.yaml
Example HTTP load testing scenario configuration                 

scenarios/kube/http_load.yaml

  • Example scenario configuration file with all supported parameters
  • Demonstrates Service targeting with configurable port and path
  • Includes optional parameters for Ingress and Route targeting
  • Configurable load parameters: duration, concurrency, RPS, HTTP method
  • Support for custom headers, request body, and content-type
  • Specifies container image and number of parallel load pods
+17/-0   

@qodo-code-review
Copy link

qodo-code-review bot commented Feb 1, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Command injection

Description: The hey command is assembled via string concatenation from user-controlled scenario fields
(e.g., target_url, content_type, header keys/values) and then executed through /bin/sh -c,
which can enable shell command injection if an attacker can craft a scenario value
containing quotes or shell metacharacters (notably target_url and header keys are not
escaped).
http_load_scenario_plugin.py [223-245]

Referred Code
def build_load_command(
    self, target_url, duration, concurrency, requests_per_second,
    http_method, request_body="", headers=None, content_type=""
):
    cmd = f"hey -c {concurrency} -z {duration}s -m {http_method}"

    if requests_per_second > 0:
        cmd += f" -q {requests_per_second}"

    if content_type:
        cmd += f" -T '{content_type}'"

    if headers:
        for key, value in headers.items():
            escaped_value = str(value).replace("'", "'\"'\"'")
            cmd += f" -H '{key}: {escaped_value}'"

    if request_body:
        escaped_body = request_body.replace("'", "'\"'\"'")
        cmd += f" -d '{escaped_body}'"



 ... (clipped 2 lines)
Ticket Compliance
🟢
🎫 #993
🟢 Add a new scenario that can simulate heavy HTTP load against a target, similar in intent
to tools like Gatling/Goku.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Swallowed exceptions: New code catches broad exceptions without capturing the exception details (or stack trace)
which removes actionable context for debugging intermittent Kubernetes API failures.

Referred Code
            except Exception:
                logging.warning(f"HttpLoadScenarioPlugin: Exception getting job status for {jobname}")

            if time.time() > wait_time:
                raise Exception("HttpLoadScenarioPlugin: Timeout waiting for jobs to complete")

        time.sleep(5)

def collect_job_metrics(self, job_list, kubecli, namespace):
    for jobname in job_list:
        try:
            api_response = kubecli.get_job_status(jobname, namespace=namespace)
            pod_name = self.get_job_pod(api_response, kubecli, namespace)

            if pod_name:
                pod_log_response = kubecli.get_pod_log(name=pod_name, namespace=namespace)
                if pod_log_response:
                    pod_log = pod_log_response.data.decode("utf-8")

                    if api_response.status.succeeded:
                        logging.info(f"HttpLoadScenarioPlugin: Metrics from job {jobname}:")


 ... (clipped 22 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Sensitive data in logs: The new implementation logs resolved_url and full pod logs/container statuses, which can
inadvertently include credentials (e.g., tokens in URLs/headers or sensitive response/log
output) and therefore risks leaking secrets into logs.

Referred Code
logging.info(f"HttpLoadScenarioPlugin: Target URL: {resolved_url}")
logging.info(
    f"HttpLoadScenarioPlugin: Duration: {duration}s, Concurrency: {concurrency}, "
    f"Pods: {number_of_pods}, Method: {http_method}"
)

file_loader = FileSystemLoader(os.path.abspath(os.path.dirname(__file__)))
env = Environment(loader=file_loader, autoescape=True)
job_template = env.get_template("job.j2")

cmd = self.build_load_command(
    resolved_url, duration, concurrency, requests_per_second,
    http_method, request_body, headers, content_type
)

job_list = []
start_time = int(time.time())
try:
    for i in range(number_of_pods):
        jobname = f"{run_uuid[:8]}-{i}"
        job_body = yaml.safe_load(


 ... (clipped 235 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Shell injection risk: User-controlled configuration fields (e.g., target_url, content_type, header keys/values,
and request_body) are concatenated into a shell command executed via "/bin/sh",
"-c", enabling command/argument injection if values contain crafted quotes or
shell metacharacters.

Referred Code
def build_load_command(
    self, target_url, duration, concurrency, requests_per_second,
    http_method, request_body="", headers=None, content_type=""
):
    cmd = f"hey -c {concurrency} -z {duration}s -m {http_method}"

    if requests_per_second > 0:
        cmd += f" -q {requests_per_second}"

    if content_type:
        cmd += f" -T '{content_type}'"

    if headers:
        for key, value in headers.items():
            escaped_value = str(value).replace("'", "'\"'\"'")
            cmd += f" -H '{key}: {escaped_value}'"

    if request_body:
        escaped_body = request_body.replace("'", "'\"'\"'")
        cmd += f" -d '{escaped_body}'"



 ... (clipped 2 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing actor context: The new logging records actions (job create/status/delete) but does not include any actor
identity context (e.g., user/initiator) needed to reconstruct who triggered the load test.

Referred Code
logging.info(f"HttpLoadScenarioPlugin: Target URL: {resolved_url}")
logging.info(
    f"HttpLoadScenarioPlugin: Duration: {duration}s, Concurrency: {concurrency}, "
    f"Pods: {number_of_pods}, Method: {http_method}"
)

file_loader = FileSystemLoader(os.path.abspath(os.path.dirname(__file__)))
env = Environment(loader=file_loader, autoescape=True)
job_template = env.get_template("job.j2")

cmd = self.build_load_command(
    resolved_url, duration, concurrency, requests_per_second,
    http_method, request_body, headers, content_type
)

job_list = []
start_time = int(time.time())
try:
    for i in range(number_of_pods):
        jobname = f"{run_uuid[:8]}-{i}"
        job_body = yaml.safe_load(


 ... (clipped 27 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Verbose exception text: Exceptions are logged via interpolated exception objects which may include internal
details from Kubernetes client errors and could be exposed depending on how logs are
surfaced to end users.

Referred Code
            except Exception as e:
                logging.error(f"HttpLoadScenarioPlugin: Exception during execution: {e}")
                return 1
            finally:
                end_time = int(time.time())
                cerberus.publish_kraken_status(krkn_config, [], start_time, end_time)
                logging.info("HttpLoadScenarioPlugin: Cleaning up jobs")
                self.delete_jobs(job_list[:], kubecli, namespace)

    except Exception as e:
        logging.error(f"HttpLoadScenarioPlugin: Exception: {e}")
        return 1
    else:
        return 0

def resolve_target_url(
    self, kubecli, target_url, target_service, target_ingress,
    target_route, target_port, target_path, namespace
):
    if target_url:
        return target_url


 ... (clipped 96 lines)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Feb 1, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Security
Fix shell injection vulnerability
Suggestion Impact:The commit added escaping of single quotes in target_url (via replace) and used the escaped value when building the shell command, mitigating the command injection risk.

code diff:

-        cmd += f" '{target_url}'"
+        escaped_url = target_url.replace("'", "'\"'\"'")
+        cmd += f" '{escaped_url}'"

Escape the target_url value before including it in the shell command to prevent
a potential command injection vulnerability.

krkn/scenario_plugins/http_load/http_load_scenario_plugin.py [244-245]

-    cmd += f" '{target_url}'"
+    escaped_url = target_url.replace("'", "'\"'\"'")
+    cmd += f" '{escaped_url}'"
     return cmd

[Suggestion processed]

Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical shell injection vulnerability and provides a correct fix, significantly improving the security of the plugin.

High
General
Validate numeric configuration

Add validation to ensure numeric configuration parameters like duration and
concurrency are positive to prevent invalid values.

krkn/scenario_plugins/http_load/http_load_scenario_plugin.py [39-43]

 duration = int(get_yaml_item_value(http_load_config, "duration", 30))
+if duration <= 0:
+    raise ValueError("duration must be positive")
 concurrency = int(get_yaml_item_value(http_load_config, "concurrency", 10))
+if concurrency <= 0:
+    raise ValueError("concurrency must be positive")
 requests_per_second = int(get_yaml_item_value(http_load_config, "requests_per_second", 0))
+if requests_per_second < 0:
+    raise ValueError("requests_per_second cannot be negative")
 number_of_pods = int(get_yaml_item_value(http_load_config, "number_of_pods", 1))
+if number_of_pods <= 0:
+    raise ValueError("number_of_pods must be positive")
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This is a valuable suggestion that improves the robustness of the plugin by adding validation for numeric inputs, preventing invalid configurations and providing clearer error messages.

Medium
Include path for direct URLs

Append the target_path to the target_url if both are specified, ensuring the
path configuration is not ignored.

krkn/scenario_plugins/http_load/http_load_scenario_plugin.py [124-125]

 if target_url:
-    return target_url
+    url = target_url.rstrip('/')
+    if target_path:
+        url += '/' + target_path.lstrip('/')
+    return url
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that target_path is ignored when target_url is provided, and implementing this change would make the configuration more intuitive and consistent.

Low
Handle Ingress with multiple rules

Add a warning log when an Ingress resource has multiple rules to clarify that
only the host from the first rule will be used.

krkn/scenario_plugins/http_load/http_load_scenario_plugin.py [155-156]

     rule = ingress.spec.rules[0]
+    if len(ingress.spec.rules) > 1:
+        logging.warning(
+            f"HttpLoadScenarioPlugin: Ingress {ingress_name} has multiple rules. "
+            f"Using the first rule with host: {rule.host}"
+        )
     host = rule.host if rule.host else "localhost"
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This is a good suggestion for improving robustness and user experience by warning about potentially ambiguous Ingress configurations, which aids in debugging.

Low
  • Update

Signed-off-by: AR21SM <mahajanashishar21sm@gmail.com>
@AR21SM AR21SM force-pushed the feature/http-load-scenario branch from 6a233a9 to 6c0e694 Compare February 1, 2026 15:36
Signed-off-by: AR21SM <mahajanashishar21sm@gmail.com>
@AR21SM
Copy link
Contributor Author

AR21SM commented Feb 1, 2026

/review

@qodo-code-review
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

993 - Partially compliant

Compliant requirements:

  • New scenario that simulates heavy HTTP load
  • Support targeting Kubernetes Service
  • Support targeting Kubernetes Ingress
  • Support targeting OpenShift Route
  • Configurable load parameters (concurrency, duration, RPS, method, headers/body)
  • Scenario implementation runnable via Kubernetes Jobs

Non-compliant requirements:

Requires further human verification:

  • Validate on a real cluster that Ingress/Route URL resolution works across typical configurations (multiple hosts/rules, path-based routing, TLS variations).
  • Validate that the created Jobs run successfully with the chosen hey image in restricted clusters (SCC/PSA, egress/network policies, image pull policies/registries).
  • Validate that reported “metrics” (logs) are sufficient/parsable for downstream reporting needs beyond log output.
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 Security concerns

Command injection:
The plugin builds a shell command string (hey ...) from scenario-provided inputs (e.g., target_url, headers, request_body, http_method) and executes it via command: ["/bin/sh", "-c", "{{cmd}}"]. Even with single-quote escaping, this remains a high-risk pattern because it relies on correct shell-escaping for all inputs and can be fragile across edge cases. Prefer running without a shell by providing an argv list to the container command/args, and validate/whitelist fields like http_method and header keys to reduce injection surface.

⚡ Recommended focus areas for review

Command Injection

User-controlled values (URL, headers, body, method, content-type) are interpolated into a shell command executed via /bin/sh -c. Single-quote escaping helps but is still fragile and can be bypassed in edge cases or via unexpected shell parsing. Prefer passing an argv list to the container command/args (no shell), or strictly validate/whitelist http_method and sanitize/encode headers/body, to prevent command injection.

            cmd = self.build_load_command(
                resolved_url, duration, concurrency, requests_per_second,
                http_method, request_body, headers, content_type
            )

            job_list = []
            start_time = int(time.time())
            try:
                for i in range(number_of_pods):
                    jobname = f"{run_uuid[:8]}-{i}"
                    job_body = yaml.safe_load(
                        job_template.render(
                            jobname=jobname,
                            namespace=namespace,
                            image=image,
                            cmd=cmd,
                        )
                    )
                    job_list.append(job_body["metadata"]["name"])
                    api_response = kubecli.create_job(job_body, namespace=namespace)
                    if api_response is None:
                        logging.error("HttpLoadScenarioPlugin: Error creating job")
                        return 1
                    logging.info(f"HttpLoadScenarioPlugin: Created job {job_body['metadata']['name']}")

                logging.info("HttpLoadScenarioPlugin: Waiting for jobs to complete")
                self.wait_for_jobs(job_list[:], kubecli, namespace, duration + 300)
                self.collect_job_metrics(job_list[:], kubecli, namespace)

            except Exception as e:
                logging.error(f"HttpLoadScenarioPlugin: Exception during execution: {e}")
                return 1
            finally:
                end_time = int(time.time())
                cerberus.publish_kraken_status(krkn_config, [], start_time, end_time)
                logging.info("HttpLoadScenarioPlugin: Cleaning up jobs")
                self.delete_jobs(job_list[:], kubecli, namespace)

    except Exception as e:
        logging.error(f"HttpLoadScenarioPlugin: Exception: {e}")
        return 1
    else:
        return 0

def resolve_target_url(
    self, kubecli, target_url, target_service, target_ingress,
    target_route, target_port, target_path, namespace
):
    if target_url:
        return target_url

    if target_service:
        if not kubecli.service_exists(target_service, namespace):
            logging.error(
                f"HttpLoadScenarioPlugin: Service {target_service} not found in namespace {namespace}"
            )
            return None
        url = f"http://{target_service}.{namespace}.svc.cluster.local:{target_port}"
        if target_path:
            url += f"/{target_path.lstrip('/')}"
        return url

    if target_ingress:
        return self.resolve_ingress_url(kubecli, target_ingress, namespace, target_path)

    if target_route:
        return self.resolve_route_url(kubecli, target_route, namespace, target_path)

    return None

def resolve_ingress_url(self, kubecli, ingress_name, namespace, target_path):
    try:
        networking_v1 = client.NetworkingV1Api(kubecli.api_client)
        ingress = networking_v1.read_namespaced_ingress(name=ingress_name, namespace=namespace)

        if not ingress.spec.rules or len(ingress.spec.rules) == 0:
            logging.error(f"HttpLoadScenarioPlugin: Ingress {ingress_name} has no rules defined")
            return None

        rule = ingress.spec.rules[0]
        host = rule.host if rule.host else "localhost"

        protocol = "http"
        if ingress.spec.tls:
            for tls in ingress.spec.tls:
                if tls.hosts and host in tls.hosts:
                    protocol = "https"
                    break

        path = target_path
        if not path and rule.http and rule.http.paths:
            path = rule.http.paths[0].path or ""

        url = f"{protocol}://{host}"
        if path:
            url += f"/{path.lstrip('/')}"

        logging.info(f"HttpLoadScenarioPlugin: Resolved Ingress {ingress_name} to {url}")
        return url

    except client.ApiException as e:
        if e.status == 404:
            logging.error(f"HttpLoadScenarioPlugin: Ingress {ingress_name} not found in namespace {namespace}")
        else:
            logging.error(f"HttpLoadScenarioPlugin: Error reading Ingress {ingress_name}: {e}")
        return None

def resolve_route_url(self, kubecli, route_name, namespace, target_path):
    try:
        custom_api = client.CustomObjectsApi(kubecli.api_client)
        route = custom_api.get_namespaced_custom_object(
            group="route.openshift.io",
            version="v1",
            namespace=namespace,
            plural="routes",
            name=route_name,
        )

        spec = route.get("spec", {})
        host = spec.get("host", "")

        if not host:
            logging.error(f"HttpLoadScenarioPlugin: Route {route_name} has no host defined")
            return None

        tls = spec.get("tls")
        protocol = "https" if tls else "http"

        path = target_path or spec.get("path", "")

        url = f"{protocol}://{host}"
        if path:
            url += f"/{path.lstrip('/')}"

        logging.info(f"HttpLoadScenarioPlugin: Resolved Route {route_name} to {url}")
        return url

    except client.ApiException as e:
        if e.status == 404:
            logging.error(
                f"HttpLoadScenarioPlugin: Route {route_name} not found in namespace {namespace}. "
                "Note: Routes are only available on OpenShift clusters."
            )
        else:
            logging.error(f"HttpLoadScenarioPlugin: Error reading Route {route_name}: {e}")
        return None

def build_load_command(
    self, target_url, duration, concurrency, requests_per_second,
    http_method, request_body="", headers=None, content_type=""
):
    cmd = f"hey -c {concurrency} -z {duration}s -m {http_method}"

    if requests_per_second > 0:
        cmd += f" -q {requests_per_second}"

    if content_type:
        escaped_content_type = content_type.replace("'", "'\"'\"'")
        cmd += f" -T '{escaped_content_type}'"

    if headers:
        for key, value in headers.items():
            escaped_value = str(value).replace("'", "'\"'\"'")
            cmd += f" -H '{key}: {escaped_value}'"

    if request_body:
        escaped_body = request_body.replace("'", "'\"'\"'")
        cmd += f" -d '{escaped_body}'"

    escaped_url = target_url.replace("'", "'\"'\"'")
    cmd += f" '{escaped_url}'"
    return cmd
Completion Logic

Job completion detection treats any non-None succeeded/failed as completion, which can be incorrect (e.g., failed=0 or succeeded=0 may be set). Consider checking that the counters are > 0, and/or use Kubernetes Job conditions (Complete/Failed) to avoid prematurely marking jobs as finished.

def wait_for_jobs(self, job_list, kubecli, namespace, timeout=300):
    wait_time = time.time() + timeout
    completed_count = 0
    total_jobs = len(job_list)
    pending_jobs = job_list[:]

    while completed_count != total_jobs:
        for jobname in pending_jobs[:]:
            try:
                api_response = kubecli.get_job_status(jobname, namespace=namespace)
                if api_response.status.succeeded is not None or api_response.status.failed is not None:
                    completed_count += 1
                    pending_jobs.remove(jobname)
                    status = "succeeded" if api_response.status.succeeded else "failed"
                    logging.info(f"HttpLoadScenarioPlugin: Job {jobname} {status}")
            except Exception as e:
                logging.warning(f"HttpLoadScenarioPlugin: Exception getting job status for {jobname}: {e}")

            if time.time() > wait_time:
                raise Exception("HttpLoadScenarioPlugin: Timeout waiting for jobs to complete")

        time.sleep(5)
Template Autoescape

The Jinja2 environment enables autoescape=True while rendering a YAML manifest. Autoescaping is generally meant for HTML/XML and can unintentionally transform characters in the rendered YAML/command string. Consider disabling autoescape for YAML templates (or using select_autoescape) to avoid subtle runtime issues.

file_loader = FileSystemLoader(os.path.abspath(os.path.dirname(__file__)))
env = Environment(loader=file_loader, autoescape=True)
job_template = env.get_template("job.j2")

Signed-off-by: AR21SM <mahajanashishar21sm@gmail.com>

def resolve_ingress_url(self, kubecli, ingress_name, namespace, target_path):
try:
networking_v1 = client.NetworkingV1Api(kubecli.api_client)
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's add this section to krkn-lib instead of in krkn where we have all of our more main kubernetes based functions


def resolve_route_url(self, kubecli, route_name, namespace, target_path):
try:
custom_api = client.CustomObjectsApi(kubecli.api_client)
Copy link
Collaborator

Choose a reason for hiding this comment

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

same as above comment about adding this to krkn-lib

@paigerube14
Copy link
Collaborator

@AR21SM if possible, are you able to add a functional test here that will create a service on the kind cluster that gets created, and then run an http test. The functional tests live here: https://github.com/krkn-chaos/krkn/tree/main/CI/tests and will need to add to the list of functional tests in the github workflow (https://krkn-chaos.dev/docs/developers-guide/add-tests-krkn/#adding-a-new-functional-test)

https://krkn-chaos.dev/docs/developers-guide/add-tests-krkn/#adding-a-new-functional-test

request_body: ""
content_type: ""
headers: {}
image: "williamyeh/hey:latest"
Copy link
Collaborator

Choose a reason for hiding this comment

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

@AR21SM can you please add documentaiton details of this new scenario into https://github.com/krkn-chaos/website/pulls. Please definitely be sure to give details of the hey utility you're using here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure

Co-authored-by: Claude Sonnet 4.5 <claude-sonnet-4-5@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP load testing scenario

3 participants