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 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=f"postgres://{pg_user}:{pg_password}@{pg_host}:{pg_port}/{pg_database}?sslmode=require",
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.constants import (
11
- DEFAULT_DEPLOYER_URL,
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
- deployed_pods: list[Any] = []
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
- deployed_pods = response.json().get("pods", [])
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((pod for pod in deployed_pods if "master" in pod), None)
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(f"{self.lambda_url}/auth/login", json=payload)
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)
@@ -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=6):
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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: locust-cloud
3
- Version: 1.5.11
3
+ Version: 1.6.0
4
4
  Summary: Locust Cloud
5
5
  Project-URL: Homepage, https://locust.cloud
6
6
  Requires-Python: >=3.11
@@ -1,11 +1,11 @@
1
- locust_cloud/__init__.py,sha256=LBL_MzFOcQ92_Oyq9xn4RXkPnQLI3uiRqc3YwFgDWHI,3216
2
- locust_cloud/auth.py,sha256=PFox9bsu6m-jCgvXcdICWvx47bGe8fQ1OU4NSLEzuvg,2975
3
- locust_cloud/cloud.py,sha256=Q7L15mpceukU3YwcyS7Kln8QaNiOtdHN15R6RDuzwU8,16332
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=KFvoThBz7fMhgZiDqId0uQ7qIoOSnDACT4yXWgSy6X8,5235
6
- locust_cloud/timescale/exporter.py,sha256=76fvl0ylLuIBL_F_cXBBrEi6QdsPuXF8eTnJafo8OEs,11460
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=NpihODXS4IvmfJL1AE7drDXOIunvsnwQVAnkcz1ypJg,2770
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.5.11.dist-info/METADATA,sha256=5NQU9A_LKm5WQCUwhe-u4c59WyN-OSAAk9Sqgz7dCWw,1664
21
- locust_cloud-1.5.11.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
22
- locust_cloud-1.5.11.dist-info/entry_points.txt,sha256=PGyAb4e3aTsGS3N3VGShDl6VzJaXy7QwsEgsLOC7V00,57
23
- locust_cloud-1.5.11.dist-info/RECORD,,
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,,