merge testing->tomerge/main #7

Open
anti wants to merge 242 commits from testing into tomerge/main
Showing only changes of commit d3f4bbb62b - Show all commits

View File

@@ -24,7 +24,9 @@ class DecnetUser(HttpUser):
wait_time = between(0.01, 0.05) # near-zero think time — max pressure wait_time = between(0.01, 0.05) # near-zero think time — max pressure
def _login_with_retry(self): def _login_with_retry(self):
"""Login with exponential backoff — handles connection storms.""" """Login with exponential backoff — handles connection storms.
Returns (access_token, must_change_password)."""
for attempt in range(_MAX_LOGIN_RETRIES): for attempt in range(_MAX_LOGIN_RETRIES):
resp = self.client.post( resp = self.client.post(
"/api/v1/auth/login", "/api/v1/auth/login",
@@ -32,7 +34,8 @@ class DecnetUser(HttpUser):
name="/api/v1/auth/login [on_start]", name="/api/v1/auth/login [on_start]",
) )
if resp.status_code == 200: if resp.status_code == 200:
return resp.json()["access_token"] body = resp.json()
return body["access_token"], bool(body.get("must_change_password", False))
# Status 0 = connection refused, retry with backoff # Status 0 = connection refused, retry with backoff
if resp.status_code == 0 or resp.status_code >= 500: if resp.status_code == 0 or resp.status_code >= 500:
time.sleep(_LOGIN_BACKOFF_BASE * (2 ** attempt)) time.sleep(_LOGIN_BACKOFF_BASE * (2 ** attempt))
@@ -41,16 +44,20 @@ class DecnetUser(HttpUser):
raise RuntimeError(f"Login failed after {_MAX_LOGIN_RETRIES} retries (last status: {resp.status_code})") raise RuntimeError(f"Login failed after {_MAX_LOGIN_RETRIES} retries (last status: {resp.status_code})")
def on_start(self): def on_start(self):
token = self._login_with_retry() token, must_change = self._login_with_retry()
# Clear must_change_password # Only pay the change-password + re-login cost on the very first run
self.client.post( # against a fresh DB. Every run after that, must_change_password is
"/api/v1/auth/change-password", # already False — skip it or the login path becomes a bcrypt storm.
json={"old_password": ADMIN_PASS, "new_password": ADMIN_PASS}, if must_change:
headers={"Authorization": f"Bearer {token}"}, self.client.post(
) "/api/v1/auth/change-password",
# Re-login for a clean token json={"old_password": ADMIN_PASS, "new_password": ADMIN_PASS},
self.token = self._login_with_retry() headers={"Authorization": f"Bearer {token}"},
)
token, _ = self._login_with_retry()
self.token = token
self.client.headers.update({"Authorization": f"Bearer {self.token}"}) self.client.headers.update({"Authorization": f"Bearer {self.token}"})
# --- Read-hot paths (high weight) --- # --- Read-hot paths (high weight) ---