locust-cloud 1.5.11__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- locust_cloud/__init__.py +13 -1
- locust_cloud/auth.py +3 -3
- locust_cloud/cloud.py +6 -6
- locust_cloud/credential_manager.py +9 -1
- locust_cloud/timescale/exporter.py +17 -0
- locust_cloud/timescale/query.py +1 -1
- {locust_cloud-1.5.11.dist-info → locust_cloud-1.6.0.dist-info}/METADATA +1 -1
- {locust_cloud-1.5.11.dist-info → locust_cloud-1.6.0.dist-info}/RECORD +10 -10
- {locust_cloud-1.5.11.dist-info → locust_cloud-1.6.0.dist-info}/WHEEL +0 -0
- {locust_cloud-1.5.11.dist-info → locust_cloud-1.6.0.dist-info}/entry_points.txt +0 -0
locust_cloud/__init__.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
import importlib.metadata
|
1
2
|
import os
|
2
3
|
|
3
4
|
os.environ["LOCUST_SKIP_MONKEY_PATCH"] = "1"
|
5
|
+
__version__ = importlib.metadata.version("locust-cloud")
|
4
6
|
|
5
7
|
import argparse
|
6
8
|
import logging
|
@@ -13,6 +15,7 @@ from locust.argument_parser import LocustArgumentParser
|
|
13
15
|
from locust_cloud.auth import register_auth
|
14
16
|
from locust_cloud.timescale.exporter import Exporter
|
15
17
|
from locust_cloud.timescale.query import register_query
|
18
|
+
from psycopg.conninfo import make_conninfo
|
16
19
|
from psycopg_pool import ConnectionPool
|
17
20
|
|
18
21
|
PG_USER = os.environ.get("PG_USER")
|
@@ -63,8 +66,17 @@ def create_connection_pool(
|
|
63
66
|
pg_user: str, pg_host: str, pg_password: str, pg_database: str, pg_port: str | int
|
64
67
|
) -> ConnectionPool:
|
65
68
|
try:
|
69
|
+
conninfo = make_conninfo(
|
70
|
+
dbname=pg_database,
|
71
|
+
user=pg_user,
|
72
|
+
port=pg_port,
|
73
|
+
password=pg_password,
|
74
|
+
host=pg_host,
|
75
|
+
sslmode="require",
|
76
|
+
options="-c statement_timeout=55000",
|
77
|
+
)
|
66
78
|
return ConnectionPool(
|
67
|
-
conninfo
|
79
|
+
conninfo,
|
68
80
|
min_size=1,
|
69
81
|
max_size=10,
|
70
82
|
configure=set_autocommit,
|
locust_cloud/auth.py
CHANGED
@@ -7,9 +7,8 @@ import requests
|
|
7
7
|
import werkzeug
|
8
8
|
from flask import redirect, request, url_for
|
9
9
|
from flask_login import UserMixin, login_user
|
10
|
-
from locust_cloud
|
11
|
-
|
12
|
-
)
|
10
|
+
from locust_cloud import __version__
|
11
|
+
from locust_cloud.constants import DEFAULT_DEPLOYER_URL
|
13
12
|
|
14
13
|
DEPLOYER_URL = os.environ.get("LOCUSTCLOUD_DEPLOYER_URL", DEFAULT_DEPLOYER_URL)
|
15
14
|
|
@@ -69,6 +68,7 @@ def register_auth(environment: locust.env.Environment):
|
|
69
68
|
auth_response = requests.post(
|
70
69
|
f"{DEPLOYER_URL}/auth/login",
|
71
70
|
json={"username": username, "password": password},
|
71
|
+
headers={"X-Client-Version": __version__},
|
72
72
|
)
|
73
73
|
|
74
74
|
if auth_response.status_code == 200:
|
locust_cloud/cloud.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import importlib.metadata
|
2
1
|
import json
|
3
2
|
import logging
|
4
3
|
import math
|
@@ -14,6 +13,7 @@ from typing import IO, Any
|
|
14
13
|
import configargparse
|
15
14
|
import requests
|
16
15
|
from botocore.exceptions import ClientError
|
16
|
+
from locust_cloud import __version__
|
17
17
|
from locust_cloud.constants import (
|
18
18
|
DEFAULT_CLUSTER_NAME,
|
19
19
|
DEFAULT_DEPLOYER_URL,
|
@@ -23,8 +23,6 @@ from locust_cloud.constants import (
|
|
23
23
|
)
|
24
24
|
from locust_cloud.credential_manager import CredentialError, CredentialManager
|
25
25
|
|
26
|
-
__version__ = importlib.metadata.version("locust-cloud")
|
27
|
-
|
28
26
|
|
29
27
|
class LocustTomlConfigParser(configargparse.TomlConfigParser):
|
30
28
|
def parse(self, stream: IO[str]) -> OrderedDict[str, Any]:
|
@@ -198,7 +196,7 @@ logging.getLogger("urllib3").setLevel(logging.INFO)
|
|
198
196
|
|
199
197
|
def main() -> None:
|
200
198
|
s3_bucket = f"{options.kube_cluster_name}-{options.kube_namespace}"
|
201
|
-
|
199
|
+
deployments: list[Any] = []
|
202
200
|
worker_count: int = max(options.workers or math.ceil(options.users / USERS_PER_WORKER), 2)
|
203
201
|
os.environ["AWS_DEFAULT_REGION"] = options.region
|
204
202
|
if options.users > 5000000:
|
@@ -305,6 +303,7 @@ def main() -> None:
|
|
305
303
|
"AWS_ACCESS_KEY_ID": aws_access_key_id,
|
306
304
|
"AWS_SECRET_ACCESS_KEY": aws_secret_access_key,
|
307
305
|
"AWS_SESSION_TOKEN": aws_session_token,
|
306
|
+
"X-Client-Version": __version__,
|
308
307
|
}
|
309
308
|
try:
|
310
309
|
# logger.info(payload) # might be useful when debugging sometimes
|
@@ -314,7 +313,7 @@ def main() -> None:
|
|
314
313
|
sys.exit(1)
|
315
314
|
|
316
315
|
if response.status_code == 200:
|
317
|
-
|
316
|
+
deployments = response.json().get("deployments", [])
|
318
317
|
else:
|
319
318
|
try:
|
320
319
|
logger.error(f"Error when deploying: {response.json()['Message']}")
|
@@ -331,7 +330,7 @@ def main() -> None:
|
|
331
330
|
sys.exit(0)
|
332
331
|
|
333
332
|
log_group_name = f"/eks/{options.kube_cluster_name}-{options.kube_namespace}"
|
334
|
-
master_pod_name = next((
|
333
|
+
master_pod_name = next((deployment for deployment in deployments if "master" in deployment), None)
|
335
334
|
|
336
335
|
if not master_pod_name:
|
337
336
|
logger.error(
|
@@ -408,6 +407,7 @@ def delete(s3_bucket, credential_manager):
|
|
408
407
|
"AWS_ACCESS_KEY_ID": refreshed_credentials.get("access_key", ""),
|
409
408
|
"AWS_SECRET_ACCESS_KEY": refreshed_credentials.get("secret_key", ""),
|
410
409
|
"Authorization": f"Bearer {refreshed_credentials.get('cognito_client_id_token', '')}",
|
410
|
+
"X-Client-Version": __version__,
|
411
411
|
}
|
412
412
|
|
413
413
|
token = refreshed_credentials.get("token")
|
@@ -8,6 +8,7 @@ import jwt
|
|
8
8
|
import requests
|
9
9
|
from botocore.credentials import RefreshableCredentials
|
10
10
|
from botocore.session import Session as BotocoreSession
|
11
|
+
from locust_cloud import __version__
|
11
12
|
|
12
13
|
logger = logging.getLogger(__name__)
|
13
14
|
|
@@ -67,7 +68,11 @@ class CredentialManager:
|
|
67
68
|
raise CredentialError("Insufficient credentials to obtain AWS session.")
|
68
69
|
|
69
70
|
try:
|
70
|
-
response = requests.post(
|
71
|
+
response = requests.post(
|
72
|
+
f"{self.lambda_url}/auth/login",
|
73
|
+
json=payload,
|
74
|
+
headers={"X-Client-Version": __version__},
|
75
|
+
)
|
71
76
|
response.raise_for_status()
|
72
77
|
data = response.json()
|
73
78
|
|
@@ -98,6 +103,9 @@ class CredentialManager:
|
|
98
103
|
if response is not None and response.status_code == 401:
|
99
104
|
raise CredentialError("Incorrect username or password.") from http_err
|
100
105
|
else:
|
106
|
+
if js := response.json():
|
107
|
+
if message := js.get("Message"):
|
108
|
+
raise CredentialError(message)
|
101
109
|
error_info = f"HTTP {response.status_code} {response.reason}" if response else "No response received."
|
102
110
|
raise CredentialError(f"HTTP error occurred while obtaining credentials: {error_info}") from http_err
|
103
111
|
except requests.exceptions.RequestException as req_err:
|
@@ -28,6 +28,7 @@ class Exporter:
|
|
28
28
|
self._run_id = None
|
29
29
|
self._samples: list[dict] = []
|
30
30
|
self._background = gevent.spawn(self._run)
|
31
|
+
self._update_end_time_task = gevent.spawn(self._update_end_time)
|
31
32
|
self._hostname = socket.gethostname()
|
32
33
|
self._finished = False
|
33
34
|
self._pid = os.getpid()
|
@@ -97,6 +98,21 @@ class Exporter:
|
|
97
98
|
break
|
98
99
|
gevent.sleep(0.5)
|
99
100
|
|
101
|
+
def _update_end_time(self):
|
102
|
+
# Regularly update endtime to prevent missing endtimes when a test crashes
|
103
|
+
while True:
|
104
|
+
current_end_time = datetime.now(UTC)
|
105
|
+
try:
|
106
|
+
with self.pool.connection() as conn:
|
107
|
+
conn.execute(
|
108
|
+
"UPDATE testruns SET end_time = %s WHERE id = %s",
|
109
|
+
(current_end_time, self._run_id),
|
110
|
+
)
|
111
|
+
gevent.sleep(60)
|
112
|
+
except psycopg.Error as error:
|
113
|
+
logging.error("Failed to update testruns table with end time: " + repr(error))
|
114
|
+
gevent.sleep(1)
|
115
|
+
|
100
116
|
def write_samples_to_db(self, samples):
|
101
117
|
try:
|
102
118
|
with self.pool.connection() as conn:
|
@@ -126,6 +142,7 @@ class Exporter:
|
|
126
142
|
self._finished = True
|
127
143
|
atexit._clear() # make sure we dont capture additional ctrl-c:s
|
128
144
|
self._background.join(timeout=10)
|
145
|
+
self._update_end_time_task.kill()
|
129
146
|
if getattr(self, "_user_count_logger", False):
|
130
147
|
self._user_count_logger.kill()
|
131
148
|
self.log_stop_test_run(exit_code)
|
locust_cloud/timescale/query.py
CHANGED
@@ -29,7 +29,7 @@ def register_query(environment, pool):
|
|
29
29
|
# protect the database against huge queries
|
30
30
|
start_time = datetime.fromisoformat(sql_params["start"])
|
31
31
|
end_time = datetime.fromisoformat(sql_params["end"])
|
32
|
-
if end_time >= start_time + timedelta(hours=
|
32
|
+
if end_time >= start_time + timedelta(hours=48):
|
33
33
|
logger.warning(
|
34
34
|
f"UI asked for too long time interval. Start was {sql_params['start']}, end was {sql_params['end']}"
|
35
35
|
)
|
@@ -1,11 +1,11 @@
|
|
1
|
-
locust_cloud/__init__.py,sha256=
|
2
|
-
locust_cloud/auth.py,sha256=
|
3
|
-
locust_cloud/cloud.py,sha256=
|
1
|
+
locust_cloud/__init__.py,sha256=jryOnEQhJSi0zmb_NItb7ic_9e5K5Jf3cmmsuingFkw,3523
|
2
|
+
locust_cloud/auth.py,sha256=aNyK2kiHa50UxNQ2NW2oomV8G6XIKCoZ1qcNMscnYiE,3062
|
3
|
+
locust_cloud/cloud.py,sha256=B-5P-Yee5BEzz7g8n3sI_wDokMBiGSvBIjtZHpOADxo,16397
|
4
4
|
locust_cloud/constants.py,sha256=75-M9cWMWOxB7RJrPccTviPz-WcMB04HatCYGC1wyRE,170
|
5
|
-
locust_cloud/credential_manager.py,sha256=
|
6
|
-
locust_cloud/timescale/exporter.py,sha256=
|
5
|
+
locust_cloud/credential_manager.py,sha256=DYfog2doA2cfDZ7tgpVOdPegkShMJXUb_jkfV8NUGyE,5528
|
6
|
+
locust_cloud/timescale/exporter.py,sha256=ZJLuGIEaCjLPtsqorpPBSkzkLrKZ4jMpeT-QW3CPwO8,12222
|
7
7
|
locust_cloud/timescale/queries.py,sha256=vwORcodOB4z_ishj6_LxBD0i7XNSYzg4IeHFn0pBF1s,6296
|
8
|
-
locust_cloud/timescale/query.py,sha256=
|
8
|
+
locust_cloud/timescale/query.py,sha256=jLnKBEueTXdwisT3PmS_oOehR-maMs_VTRi7k46rzkE,2771
|
9
9
|
locust_cloud/webui/.eslintrc,sha256=huuuTZNwbBGkhfelEOMhLQ-tgC9OYnnX0DRYgBn83Ig,1146
|
10
10
|
locust_cloud/webui/.gitignore,sha256=i8EHIqDlVm1-Dkvf_GTZmP_Wu99GE7ABfbYzHXOU734,54
|
11
11
|
locust_cloud/webui/.prettierrc,sha256=qZpzAJPlHUQ3--NHYK8WXgc_TlIqq-eoz4pbkTKK3nk,167
|
@@ -17,7 +17,7 @@ locust_cloud/webui/vite.config.ts,sha256=cqxPMkbwEA3H9mGGbuPulQUhIHCosUqm_1usxzs
|
|
17
17
|
locust_cloud/webui/yarn.lock,sha256=YxZP905geusJV48VSr2lG2HrPyegUExE5CWmH8dS2Bg,227118
|
18
18
|
locust_cloud/webui/dist/index.html,sha256=Z8x1932WXhLeHfvIVejfB3SxXI6wQP028GGl5VnkafM,664
|
19
19
|
locust_cloud/webui/dist/assets/index-BrOP_HSY.js,sha256=GT8kkxuuVsbaVj-9KGFb6WTmoZ9hJm2eUP4LmZ7zUhU,2809361
|
20
|
-
locust_cloud-1.
|
21
|
-
locust_cloud-1.
|
22
|
-
locust_cloud-1.
|
23
|
-
locust_cloud-1.
|
20
|
+
locust_cloud-1.6.0.dist-info/METADATA,sha256=tSYi56wjgFFvqxG9k8dTUeWRhr73v0AyJJLWtV8OjvI,1663
|
21
|
+
locust_cloud-1.6.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
22
|
+
locust_cloud-1.6.0.dist-info/entry_points.txt,sha256=PGyAb4e3aTsGS3N3VGShDl6VzJaXy7QwsEgsLOC7V00,57
|
23
|
+
locust_cloud-1.6.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|