Skip to content

Commit ab47aba

Browse files
authored
chore: mv load tests (#2060)
chore: mv load tests
1 parent cfb3e5c commit ab47aba

9 files changed

Lines changed: 3182 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Mozilla AutoPush Load-Tester
2+
3+
# SyncStorage-LoadTest
4+
#
5+
FROM python:3.10-slim
6+
7+
RUN mkdir -p /app
8+
ADD . /app
9+
WORKDIR /app
10+
11+
# Building:
12+
# you can build a local docker image using
13+
# `docker build . --tag syncstorage-loadtest:local`
14+
15+
# system setup
16+
RUN \
17+
BUILD_DEPS="git build-essential" && \
18+
# wget not required but nice to have
19+
RUN_DEPS="wget libssl-dev" && \
20+
apt-get update && \
21+
apt-get install -yq --no-install-recommends ${BUILD_DEPS} ${RUN_DEPS} && \
22+
pip install --no-cache-dir poetry && \
23+
apt-get purge -yq --auto-remove ${BUILD_DEPS} && \
24+
apt-get autoremove -yqq && \
25+
apt-get clean -y
26+
27+
# app install
28+
RUN poetry config virtualenvs.create false && \
29+
poetry install --no-dev --no-interaction --no-ansi
30+
31+
# Using:
32+
# Start an interactive terminal using
33+
# `docker run --net=host -it syncstorage-loadtest:local`
34+
# This will start a bash shell as root.
35+
# You can fire off a load test by calling:
36+
# `SERVER_URL=http://${HOST}:${PORT}#${SECRET} molotov -v`
37+
38+
ENTRYPOINT ["/bin/bash"]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Storage load test
2+
3+
**Note:** This requires Python version 3.10+
4+
5+
The tests can execute in three modes:
6+
7+
1. **Direct access**: use a known master secret
8+
2. **Firefox accounts OAuth**: create test accounts and use OAuth JWTs
9+
3. **Self-signed JWT**: generate and sign JWTs locally with a given private key
10+
11+
## Installation
12+
13+
### Environment Setup
14+
15+
To run the syncstorage load tests, you'll need a Python >=3.10 development environment with `Poetry` installed. You can also directly use `poetry run` to execute commands as described in the usage examples below.
16+
17+
The easiest solution is to use `pyenv` and the `pyenv-virtualenv` plugin for your virtual environments as a way to isolate the dependencies from other projects.
18+
19+
1. Install `pyenv` using the [latest documentation](https://github.com/pyenv/pyenv#installation) for your platform.
20+
21+
2. Follow the instructions to install the `pyenv-virtualenv` plugin.
22+
See the [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) documentation.
23+
24+
3. Ensure you've added `pyenv` and `pyenv-virtualenv` to your PATH.
25+
26+
Example:
27+
```shell
28+
export PATH="$HOME/.pyenv/bin:$PATH"
29+
eval "$(pyenv init -)"
30+
eval "$(pyenv virtualenv-init -)"
31+
```
32+
33+
4. Install Python version, create virtualenv, activate and install dependencies from inside the project directory.
34+
35+
**Note:** You can skip creating a virtual environment and invoke commands directly using `poetry run`.
36+
37+
```shell
38+
$ cd syncstorage-loadtest
39+
40+
# Install Python 3.10+
41+
$ pyenv install 3.10
42+
43+
# Create named, associated virtualenv
44+
$ pyenv virtualenv 3.10 syncstorage-loadtest # or whatever name you prefer
45+
$ pyenv local syncstorage-loadtest # activates virtual env whenever you enter this directory
46+
47+
# Install Poetry and dependencies
48+
$ pip install poetry
49+
$ poetry install
50+
```
51+
52+
5. Once you're in your virtual environment, run the load tests using:
53+
```bash
54+
poetry run molotov [options] loadtest.py
55+
```
56+
57+
### Quick Install
58+
59+
If you already have Poetry installed:
60+
61+
```bash
62+
poetry install
63+
```
64+
65+
## Mode 1: Direct Access
66+
67+
With a known syncstorage master secret:
68+
69+
```bash
70+
SERVER_URL="http://localhost:8000#secretValue" poetry run molotov --max-runs 5 -cxv loadtest.py
71+
```
72+
73+
## Mode 2: Firefox Accounts OAuth
74+
75+
With FxA stage accounts:
76+
77+
```bash
78+
SERVER_URL="https://token.stage.mozaws.net" \
79+
FXA_API_HOST="https://api-accounts.stage.mozaws.net" \
80+
FXA_OAUTH_HOST="https://oauth.stage.mozaws.net" \
81+
poetry run molotov --workers 3 --duration 60 -v loadtest.py
82+
```
83+
84+
## Mode 3: Self-Signed JWTs
85+
86+
Assuming an RSA keypair was generated, the private key pem was saved and accessible, and the token server was started with the public key JWK configured. The presence of `OAUTH_PRIVATE_KEY_FILE` triggers this mode when using OAuth.
87+
88+
Run with self-signed JWTs:
89+
90+
```bash
91+
SERVER_URL="http://localhost:8000" \
92+
OAUTH_PRIVATE_KEY_FILE="/path/to/load_test.pem" \
93+
poetry run molotov --workers 100 --duration 300 -v loadtest.py
94+
```
95+
96+
## Docker
97+
98+
To run it inside docker:
99+
100+
```bash
101+
docker run -e TEST_REPO=https://github.com/mozilla-services/syncstorage-loadtest -e TEST_NAME=test tarekziade/molotov:latest
102+
```
103+
104+
Happy Breaking!
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Cleanup orphaned accounts from interrupted tests.
4+
5+
Usage:
6+
python cleanup_orphaned_accounts.py
7+
"""
8+
9+
import os
10+
import json
11+
from fxa.core import Client
12+
from fxa.errors import ClientError, ServerError
13+
14+
ACCT_TRACKING_FILE = os.path.join(os.path.dirname(__file__), ".accounts_tracking.json")
15+
FXA_API_HOST = os.environ.get("FXA_API_HOST", "https://api-accounts.stage.mozaws.net")
16+
17+
18+
def load_tracked_accounts():
19+
if not os.path.exists(ACCT_TRACKING_FILE):
20+
return []
21+
22+
try:
23+
with open(ACCT_TRACKING_FILE, "r") as f:
24+
return json.load(f)
25+
except (json.JSONDecodeError, IOError) as e:
26+
print(f"Warning: Could not load tracking file: {e}")
27+
return []
28+
29+
30+
def save_tracked_accounts(accounts):
31+
try:
32+
if not accounts:
33+
if os.path.exists(ACCT_TRACKING_FILE):
34+
os.remove(ACCT_TRACKING_FILE)
35+
else:
36+
with open(ACCT_TRACKING_FILE, "w") as f:
37+
json.dump(accounts, f, indent=2)
38+
except IOError as e:
39+
print(f"Warning: Could not save tracking file: {e}")
40+
raise
41+
42+
43+
def remove_account_from_tracking(email):
44+
accounts = load_tracked_accounts()
45+
accounts = [acc for acc in accounts if acc["email"] != email]
46+
save_tracked_accounts(accounts)
47+
48+
49+
def cleanup_account(client, account):
50+
email = account["email"]
51+
password = account["password"]
52+
53+
try:
54+
client.destroy_account(email, password)
55+
print(f" ✓ Deleted: {email}")
56+
return True
57+
except (ServerError, ClientError) as ex:
58+
print(f" ✗ Delete failed: {email} - {ex}")
59+
return False
60+
except Exception as ex:
61+
print(f" ✗ Delete error: {email} - {ex}")
62+
return False
63+
64+
65+
def cleanup_all_accounts():
66+
accounts = load_tracked_accounts()
67+
68+
if not accounts:
69+
print("No accounts to clean up.")
70+
return 0, 0
71+
72+
print(f"\nFound {len(accounts)} accounts")
73+
print("\nAttempting to delete accounts...\n")
74+
75+
client = Client(FXA_API_HOST)
76+
successful = 0
77+
failed = 0
78+
79+
for account in accounts:
80+
if cleanup_account(client, account):
81+
successful += 1
82+
remove_account_from_tracking(account["email"])
83+
else:
84+
failed += 1
85+
86+
print(f"\nResults: {successful} deleted, {failed} failed")
87+
88+
return successful, failed
89+
90+
91+
def main():
92+
cleanup_all_accounts()
93+
94+
95+
if __name__ == "__main__":
96+
main()

0 commit comments

Comments
 (0)