Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
94138ed
feat: add API_AUTH_TOKEN auth and POST /stop endpoint (#91, #93)
cbaugus Mar 3, 2026
8d3dafd
feat: add optional tenant label to metrics and POST /stop scoping
cbaugus Mar 3, 2026
c4250e8
fix: resolve E0425 (new_tenant used before definition) and rustfmt vi…
cbaugus Mar 3, 2026
bd5bccb
fix: rustfmt chain splits and convert flaky httpbin test to wiremock
cbaugus Mar 3, 2026
062a4de
fix: add tenant label to integration test metric reads
cbaugus Mar 3, 2026
d3fce07
fix: convert flaky httpbin tests to wiremock in http_methods_tests
cbaugus Mar 3, 2026
739d7ae
feat: health auth (#92), node auto-registration (#89), acceptable use…
cbaugus Mar 9, 2026
816ff30
feat: migrate subcommand (#42) and weekly Docker build workflow (#25)
cbaugus Mar 9, 2026
52f4cdb
fix: rustfmt violations in registry.rs and main.rs
cbaugus Mar 9, 2026
5c065d9
fix: rustfmt — collapse env_or closure to single line
cbaugus Mar 9, 2026
5c1d043
feat: body_size field for synthetic large-payload load testing (close…
cbaugus Mar 9, 2026
94767cf
fix: restore /// prefix in scenario.rs doc comment, split assert_eq! …
cbaugus Mar 9, 2026
a1ed2bf
fix: rustfmt — remove duplicate body_size field, fix headers indentat…
cbaugus Mar 9, 2026
b8f3aeb
fix: remove duplicate body_size field in scenario.rs test literal
cbaugus Mar 9, 2026
c432bfb
fix: add body_size: None to RequestConfig literals in scenario_exampl…
cbaugus Mar 9, 2026
0e6a70f
fix: parse_body_size correctly rejects unknown units like GB/TB
cbaugus Mar 9, 2026
c6d1e3e
chore: add 1MB bodySize to step 2 in consul-kv-config-example.yaml
cbaugus Mar 9, 2026
e621925
docs: document bodySize feature in Docker Hub overview and examples/c…
cbaugus Mar 9, 2026
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
123 changes: 123 additions & 0 deletions .github/workflows/weekly-docker-build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Weekly Docker Build

# Rebuilds Docker images from main every Monday at 00:00 UTC so the
# Chainguard base image picks up the latest OS security patches even
# when no code changes have been pushed that week.
#
# Can also be triggered manually via the Actions UI.

on:
schedule:
- cron: '0 0 * * 1' # Monday 00:00 UTC
workflow_dispatch: # Manual trigger

env:
IMAGE: cbaugus/rust_loadtest

jobs:
weekly-build:
name: Build and push weekly Docker images
runs-on: ubuntu-latest
# Only run on main branch (schedule always targets default branch,
# but guard against accidental workflow_dispatch on a feature branch).
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'

steps:
- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: main

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Generate date and week tags
id: tags
run: |
echo "date=$(date +%Y-%m-%d)" >> $GITHUB_OUTPUT
echo "week=$(date +weekly-%Y-%U)" >> $GITHUB_OUTPUT

# ── Standard (Ubuntu) image ─────────────────────────────────────────────
- name: Build and push standard image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: |
${{ env.IMAGE }}:latest
${{ env.IMAGE }}:${{ steps.tags.outputs.date }}
${{ env.IMAGE }}:${{ steps.tags.outputs.week }}
cache-from: type=registry,ref=${{ env.IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.IMAGE }}:buildcache,mode=max

# ── Chainguard (distroless) image ────────────────────────────────────────
- name: Build and push Chainguard image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile.chainguard
push: true
tags: |
${{ env.IMAGE }}:latest-Chainguard
${{ env.IMAGE }}:${{ steps.tags.outputs.date }}-Chainguard
${{ env.IMAGE }}:${{ steps.tags.outputs.week }}-Chainguard
cache-from: type=registry,ref=${{ env.IMAGE }}:buildcache-chainguard
cache-to: type=registry,ref=${{ env.IMAGE }}:buildcache-chainguard,mode=max

# ── SBOMs ────────────────────────────────────────────────────────────────
- name: Install Syft
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

- name: Generate SBOM — standard image
run: |
syft "docker:${{ env.IMAGE }}:${{ steps.tags.outputs.date }}" \
-o cyclonedx-json \
> sbom-standard-${{ steps.tags.outputs.date }}.json

- name: Generate SBOM — Chainguard image
run: |
syft "docker:${{ env.IMAGE }}:${{ steps.tags.outputs.date }}-Chainguard" \
-o cyclonedx-json \
> sbom-chainguard-${{ steps.tags.outputs.date }}.json

# ── Vulnerability scan ───────────────────────────────────────────────────
- name: Scan standard image (Trivy)
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE }}:${{ steps.tags.outputs.date }}
format: sarif
output: trivy-standard.sarif
severity: CRITICAL,HIGH
continue-on-error: true # don't fail the build; results go to Security tab

- name: Scan Chainguard image (Trivy)
uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE }}:${{ steps.tags.outputs.date }}-Chainguard
format: sarif
output: trivy-chainguard.sarif
severity: CRITICAL,HIGH
continue-on-error: true

- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: trivy-standard.sarif
continue-on-error: true

# ── Upload artifacts ─────────────────────────────────────────────────────
- name: Upload SBOMs
uses: actions/upload-artifact@v4
with:
name: sbom-${{ steps.tags.outputs.date }}
path: |
sbom-standard-${{ steps.tags.outputs.date }}.json
sbom-chainguard-${{ steps.tags.outputs.date }}.json
retention-days: 90
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ Thumbs.db
# Business documents (keep local only)
PRODUCT_DESIGN.md
BUSINESS_PLAN.md
Product_Readness_Review.md

# Local phase/project status notes
phase3_status.md
next_steps.md

# Phase 3 web app design (separate repo — local planning only)
phase3_web_app/

# Local/personal Nomad job files (environment-specific, not examples)
nomad/loadtest.nomad.hcl
54 changes: 54 additions & 0 deletions ACCEPTABLE_USE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Acceptable Use Policy

## rust_loadtest

This software is a load testing tool designed to help engineers measure and
understand the performance of HTTP services they own or are authorized to test.

---

## Permitted Use

You may use rust_loadtest to test:

- Systems you own
- Systems operated by your employer, where your role includes performance testing
- Systems for which you have explicit written permission from the owner to conduct
load or stress testing

---

## Prohibited Use

You may **not** use rust_loadtest to:

- Send traffic to any system without the explicit permission of its owner
- Conduct denial-of-service (DoS) or distributed denial-of-service (DDoS) attacks
- Disrupt, degrade, or interfere with the availability of any service, network,
or infrastructure
- Circumvent rate limits, access controls, or security measures on systems you
do not own
- Test third-party services, APIs, or websites without written authorization,
regardless of whether those services are publicly accessible

---

## Your Responsibility

By running this software you accept full responsibility for:

- Ensuring you have authorization to generate load against the target system
- Any damage, disruption, or legal consequence resulting from your use
- Compliance with all applicable laws and regulations in your jurisdiction,
including the Computer Fraud and Abuse Act (CFAA), the Computer Misuse Act
(CMA), and equivalent statutes

The authors and contributors of rust_loadtest bear no liability for any harm
caused by unauthorized or malicious use of this software.

---

## Reporting Misuse

If you observe rust_loadtest being used to attack or disrupt systems, please
report it to: **cbaugus@users.noreply.github.com**
61 changes: 61 additions & 0 deletions DOCKER_HUB_OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,67 @@ Send JSON data with POST requests:
-e JSON_PAYLOAD='{"key":"value","nested":{"data":"here"}}'
```

### Large Payload / Upload Testing (`bodySize`)

Stress-test upload endpoints or request-body parsing by sending a synthetic payload of a specified size on every request — no need to inline large strings in your config.

Supported units: `B`, `KB`, `MB`

```yaml
scenarios:
- name: "Upload stress test"
weight: 100
steps:
- name: "POST 1MB body"
request:
method: "POST"
path: "/api/upload"
bodySize: "1MB" # generates 1 048 576 random bytes per request
headers:
Content-Type: "application/octet-stream"
assertions:
- type: statusCode
expected: 200
- type: responseTime
max: "5s"
```

`bodySize` and `body` are mutually exclusive — use one or the other per step.

**Combined with JWT auth (multi-step):**
```yaml
scenarios:
- name: "Auth then upload"
weight: 100
steps:
- name: "Login"
request:
method: "POST"
path: "/auth/login"
body: '{"username":"loadtest","password":"secret"}'
headers:
Content-Type: "application/json"
extract:
- type: jsonPath
name: "jwt_token"
jsonPath: "$.token"
assertions:
- type: statusCode
expected: 200

- name: "Upload 512KB"
request:
method: "POST"
path: "/api/upload"
bodySize: "512KB"
headers:
Authorization: "Bearer ${jwt_token}"
Content-Type: "application/octet-stream"
assertions:
- type: statusCode
expected: 200
```

## Live Control API (port 8080)

Every node exposes a lightweight HTTP API for real-time inspection and reconfiguration.
Expand Down
49 changes: 47 additions & 2 deletions examples/configs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,39 @@ rust-loadtest --config graphql-api.yaml

---

### 8. Spike Test (`spike-test.yaml`)
### 8. Large Payload Test (`large-payload-test.yaml`)

**Purpose**: Stress-test endpoints that accept large request bodies (uploads, multipart, binary ingestion)

**Use Cases**:
- Upload endpoint capacity testing
- Request-body parser stress testing
- Memory pressure under large payloads
- Bandwidth / throughput validation

**Key Features**:
- `bodySize` field generates synthetic random data per request — no YAML bloat
- Supports `B`, `KB`, `MB` units
- Two scenarios: raw upload and JWT-authenticated upload
- `bodySize` and `body` are mutually exclusive per step

**Quick Start**:
```bash
# Edit baseUrl, then push config to a running node
curl -X POST http://<node>:8080/config \
-H "Content-Type: application/x-yaml" \
--data-binary @large-payload-test.yaml
```

**Customize**:
- `bodySize`: Change size (`"128KB"`, `"512KB"`, `"1MB"`)
- `path`: Point to your upload endpoint
- `target`: Adjust RPS (start low — large bodies use significant bandwidth)
- Remove the auth scenario if your endpoint is unauthenticated

---

### 9. Spike Test (`spike-test.yaml`)

**Purpose**: Sudden traffic spike test for resilience validation

Expand Down Expand Up @@ -310,6 +342,7 @@ rust-loadtest --config spike-test.yaml
| Authenticated | Medium | 20m | 25 | 75 | Auth flows, token management |
| Microservices | High | 30m | 40 | 20-150 | Distributed systems, multiple services |
| GraphQL | Medium | 20m | 30 | 80 | GraphQL APIs, complex queries |
| Large Payload | Medium | 10m | 20 | 50 | Upload endpoints, body-parser stress |
| Spike Test | High | 30m | 150 | Burst | Resilience, auto-scaling |

## Customization Guide
Expand Down Expand Up @@ -383,7 +416,19 @@ thinkTime:
max: "5s"
```

#### 6. Add Custom Assertions
#### 6. Send Large Synthetic Payloads
```yaml
steps:
- name: "Upload 1MB"
request:
method: "POST"
path: "/api/upload"
bodySize: "1MB" # B, KB, or MB — mutually exclusive with `body`
headers:
Content-Type: "application/octet-stream"
```

#### 7. Add Custom Assertions
```yaml
assertions:
- statusCode: 200
Expand Down
Loading