locust-cloud 1.9.0__py3-none-any.whl → 1.10.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 +31 -21
- locust_cloud/auth.py +29 -22
- locust_cloud/cloud.py +20 -11
- locust_cloud/timescale/exporter.py +2 -3
- locust_cloud/timescale/queries.py +1 -1
- locust_cloud/webui/dist/assets/{index-DVpRWzFO.js → index-DkFumpSS.js} +34 -34
- locust_cloud/webui/dist/index.html +1 -1
- locust_cloud/webui/tsconfig.tsbuildinfo +1 -1
- {locust_cloud-1.9.0.dist-info → locust_cloud-1.10.0.dist-info}/METADATA +1 -1
- locust_cloud-1.10.0.dist-info/RECORD +23 -0
- {locust_cloud-1.9.0.dist-info → locust_cloud-1.10.0.dist-info}/WHEEL +1 -1
- locust_cloud-1.9.0.dist-info/RECORD +0 -23
- {locust_cloud-1.9.0.dist-info → locust_cloud-1.10.0.dist-info}/entry_points.txt +0 -0
locust_cloud/__init__.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
import importlib.metadata
|
2
2
|
import os
|
3
|
+
import sys
|
3
4
|
|
4
5
|
os.environ["LOCUST_SKIP_MONKEY_PATCH"] = "1"
|
5
6
|
__version__ = importlib.metadata.version("locust-cloud")
|
6
7
|
|
7
|
-
import argparse
|
8
8
|
import logging
|
9
9
|
|
10
10
|
import configargparse
|
@@ -19,38 +19,29 @@ from locust_cloud.timescale.query import register_query
|
|
19
19
|
from psycopg.conninfo import make_conninfo
|
20
20
|
from psycopg_pool import ConnectionPool
|
21
21
|
|
22
|
-
GRAPH_VIEWER = os.environ.get("GRAPH_VIEWER")
|
23
22
|
logger = logging.getLogger(__name__)
|
24
23
|
|
25
24
|
|
26
25
|
@events.init_command_line_parser.add_listener
|
27
26
|
def add_arguments(parser: LocustArgumentParser):
|
28
|
-
if not (os.environ.get("PGHOST")
|
27
|
+
if not (os.environ.get("PGHOST")):
|
29
28
|
parser.add_argument_group(
|
30
29
|
"locust-cloud",
|
31
30
|
"locust-cloud disabled, because PGHOST was not set - this is normal for local runs",
|
32
31
|
)
|
33
32
|
return
|
34
33
|
|
34
|
+
try:
|
35
|
+
REGION = os.environ["AWS_DEFAULT_REGION"]
|
36
|
+
except KeyError:
|
37
|
+
logger.fatal("Missing AWS_DEFAULT_REGION env var")
|
38
|
+
sys.exit(1)
|
39
|
+
|
35
40
|
os.environ["LOCUST_BUILD_PATH"] = os.path.join(os.path.dirname(__file__), "webui/dist")
|
36
41
|
locust_cloud = parser.add_argument_group(
|
37
42
|
"locust-cloud",
|
38
43
|
"Arguments for use with Locust cloud",
|
39
44
|
)
|
40
|
-
locust_cloud.add_argument(
|
41
|
-
"--exporter",
|
42
|
-
default=True,
|
43
|
-
action=argparse.BooleanOptionalAction,
|
44
|
-
env_var="LOCUST_EXPORTER",
|
45
|
-
help="Exports Locust stats to Timescale",
|
46
|
-
)
|
47
|
-
locust_cloud.add_argument(
|
48
|
-
"--description",
|
49
|
-
type=str,
|
50
|
-
env_var="LOCUST_DESCRIPTION",
|
51
|
-
default="",
|
52
|
-
help="Description of the test being run",
|
53
|
-
)
|
54
45
|
# do not set
|
55
46
|
# used for sending the run id from master to workers
|
56
47
|
locust_cloud.add_argument(
|
@@ -59,6 +50,27 @@ def add_arguments(parser: LocustArgumentParser):
|
|
59
50
|
env_var="LOCUSTCLOUD_RUN_ID",
|
60
51
|
help=configargparse.SUPPRESS,
|
61
52
|
)
|
53
|
+
locust_cloud.add_argument(
|
54
|
+
"--allow-signup",
|
55
|
+
env_var="LOCUSTCLOUD_ALLOW_SIGNUP",
|
56
|
+
help=configargparse.SUPPRESS,
|
57
|
+
default=False,
|
58
|
+
action="store_true",
|
59
|
+
)
|
60
|
+
locust_cloud.add_argument(
|
61
|
+
"--graph-viewer",
|
62
|
+
env_var="LOCUSTCLOUD_GRAPH_VIEWER",
|
63
|
+
help=configargparse.SUPPRESS,
|
64
|
+
default=False,
|
65
|
+
action="store_true",
|
66
|
+
)
|
67
|
+
locust_cloud.add_argument(
|
68
|
+
"--deployer-url",
|
69
|
+
type=str,
|
70
|
+
env_var="LOCUSTCLOUD_DEPLOYER_URL",
|
71
|
+
help=configargparse.SUPPRESS,
|
72
|
+
default=f"https://api.{REGION}.locust.cloud/1",
|
73
|
+
)
|
62
74
|
|
63
75
|
|
64
76
|
def set_autocommit(conn: psycopg.Connection):
|
@@ -86,14 +98,12 @@ def on_locust_init(environment: locust.env.Environment, **_args):
|
|
86
98
|
logger.exception(e)
|
87
99
|
raise
|
88
100
|
|
89
|
-
if not
|
101
|
+
if not environment.parsed_options.graph_viewer:
|
90
102
|
IdleExit(environment)
|
91
|
-
|
92
|
-
if not GRAPH_VIEWER and environment.parsed_options and environment.parsed_options.exporter:
|
93
103
|
Exporter(environment, pool)
|
94
104
|
|
95
105
|
if environment.web_ui:
|
96
|
-
if
|
106
|
+
if environment.parsed_options.graph_viewer:
|
97
107
|
environment.web_ui.template_args["isGraphViewer"] = True
|
98
108
|
|
99
109
|
register_auth(environment)
|
locust_cloud/auth.py
CHANGED
@@ -11,11 +11,6 @@ from flask_login import UserMixin, login_user
|
|
11
11
|
from locust.html import render_template_from
|
12
12
|
from locust_cloud import __version__
|
13
13
|
|
14
|
-
REGION = os.environ.get("AWS_DEFAULT_REGION")
|
15
|
-
DEPLOYER_URL = f"https://api.{REGION}.locust.cloud/1"
|
16
|
-
ALLOW_SIGNUP = os.environ.get("ALLOW_SIGNUP", True)
|
17
|
-
|
18
|
-
|
19
14
|
logger = logging.getLogger(__name__)
|
20
15
|
|
21
16
|
|
@@ -47,7 +42,7 @@ def set_credentials(username: str, credentials: Credentials, response: werkzeug.
|
|
47
42
|
|
48
43
|
|
49
44
|
def register_auth(environment: locust.env.Environment):
|
50
|
-
environment.web_ui.app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
|
45
|
+
environment.web_ui.app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", "") + os.getenv("CUSTOMER_ID", "")
|
51
46
|
environment.web_ui.app.debug = False
|
52
47
|
|
53
48
|
web_base_path = environment.parsed_options.web_base_path
|
@@ -71,7 +66,7 @@ def register_auth(environment: locust.env.Environment):
|
|
71
66
|
},
|
72
67
|
)
|
73
68
|
|
74
|
-
if
|
69
|
+
if environment.parsed_options.allow_signup:
|
75
70
|
environment.web_ui.auth_args["auth_providers"] = [
|
76
71
|
{"label": "Sign Up", "callback_url": f"{web_base_path}/signup"}
|
77
72
|
]
|
@@ -83,7 +78,7 @@ def register_auth(environment: locust.env.Environment):
|
|
83
78
|
|
84
79
|
try:
|
85
80
|
auth_response = requests.post(
|
86
|
-
f"{
|
81
|
+
f"{environment.parsed_options.deployer_url}/auth/login",
|
87
82
|
json={"username": username, "password": password},
|
88
83
|
headers={"X-Client-Version": __version__},
|
89
84
|
)
|
@@ -91,6 +86,11 @@ def register_auth(environment: locust.env.Environment):
|
|
91
86
|
auth_response.raise_for_status()
|
92
87
|
|
93
88
|
credentials = auth_response.json()
|
89
|
+
|
90
|
+
if os.getenv("CUSTOMER_ID", "") and credentials["user_sub_id"] != os.getenv("CUSTOMER_ID", ""):
|
91
|
+
session["auth_error"] = "Invalid login for this deployment"
|
92
|
+
return redirect(url_for("locust.login"))
|
93
|
+
|
94
94
|
response = redirect(url_for("locust.index"))
|
95
95
|
response = set_credentials(username, credentials, response)
|
96
96
|
login_user(AuthUser(credentials["user_sub_id"]))
|
@@ -108,7 +108,7 @@ def register_auth(environment: locust.env.Environment):
|
|
108
108
|
|
109
109
|
@auth_blueprint.route("/signup")
|
110
110
|
def signup():
|
111
|
-
if not
|
111
|
+
if not environment.parsed_options.allow_signup:
|
112
112
|
return redirect(url_for("locust.login"))
|
113
113
|
|
114
114
|
if session.get("username"):
|
@@ -118,6 +118,7 @@ def register_auth(environment: locust.env.Environment):
|
|
118
118
|
{
|
119
119
|
"label": "Confirmation Code",
|
120
120
|
"name": "confirmation_code",
|
121
|
+
"is_required": True,
|
121
122
|
},
|
122
123
|
],
|
123
124
|
"callback_url": f"{web_base_path}/confirm-signup",
|
@@ -131,20 +132,29 @@ def register_auth(environment: locust.env.Environment):
|
|
131
132
|
{
|
132
133
|
"label": "Username",
|
133
134
|
"name": "username",
|
135
|
+
"is_required": True,
|
136
|
+
},
|
137
|
+
{
|
138
|
+
"label": "Full Name",
|
139
|
+
"name": "full_name",
|
140
|
+
"is_required": True,
|
134
141
|
},
|
135
142
|
{
|
136
143
|
"label": "Password",
|
137
144
|
"name": "password",
|
138
145
|
"is_secret": True,
|
146
|
+
"is_required": True,
|
139
147
|
},
|
140
148
|
{
|
141
149
|
"label": "Access Code",
|
142
150
|
"name": "access_code",
|
151
|
+
"is_required": True,
|
143
152
|
},
|
144
153
|
{
|
145
|
-
"label": "I consent to:\n\n1. Only test your own website/service or our example
|
154
|
+
"label": "I consent to:\n\n1. Only test your own website/service or our example target\n\n2. Only use locust-cloud for its intended purpose: to load test other sites/services.\n\n3. Not attempt to circumvent your account limitations (e.g. max user count or max request count)\n\n4. Not use personal data (real names, addresses etc) in your tests.",
|
146
155
|
"name": "consent",
|
147
156
|
"default_value": False,
|
157
|
+
"is_required": True,
|
148
158
|
},
|
149
159
|
],
|
150
160
|
"callback_url": f"{web_base_path}/create-account",
|
@@ -164,25 +174,20 @@ def register_auth(environment: locust.env.Environment):
|
|
164
174
|
|
165
175
|
@auth_blueprint.route("/create-account", methods=["POST"])
|
166
176
|
def create_account():
|
167
|
-
if not
|
177
|
+
if not environment.parsed_options.allow_signup:
|
168
178
|
return redirect(url_for("locust.login"))
|
169
179
|
|
170
180
|
session["auth_sign_up_error"] = ""
|
171
181
|
session["auth_info"] = ""
|
172
182
|
|
173
183
|
username = request.form.get("username", "")
|
184
|
+
full_name = request.form.get("full_name", "")
|
174
185
|
password = request.form.get("password")
|
175
186
|
access_code = request.form.get("access_code")
|
176
|
-
has_consented = request.form.get("consent")
|
177
|
-
|
178
|
-
if not has_consented:
|
179
|
-
session["auth_sign_up_error"] = "Please accept the terms and conditions to create an account."
|
180
|
-
|
181
|
-
return redirect(url_for("locust_cloud_auth.signup"))
|
182
187
|
|
183
188
|
try:
|
184
189
|
auth_response = requests.post(
|
185
|
-
f"{
|
190
|
+
f"{environment.parsed_options.deployer_url}/auth/signup",
|
186
191
|
json={"username": username, "password": password, "access_code": access_code},
|
187
192
|
)
|
188
193
|
|
@@ -190,6 +195,7 @@ def register_auth(environment: locust.env.Environment):
|
|
190
195
|
|
191
196
|
session["user_sub"] = auth_response.json().get("user_sub")
|
192
197
|
session["username"] = username
|
198
|
+
session["full_name"] = full_name
|
193
199
|
session["auth_info"] = (
|
194
200
|
"Please check your email and enter the confirmation code. If you didn't get a code after one minute, you can [request a new one](/resend-code)"
|
195
201
|
)
|
@@ -206,7 +212,7 @@ def register_auth(environment: locust.env.Environment):
|
|
206
212
|
def resend_code():
|
207
213
|
try:
|
208
214
|
auth_response = requests.post(
|
209
|
-
"
|
215
|
+
f"{environment.parsed_options.deployer_url}/1/auth/resend-confirmation",
|
210
216
|
json={"username": session["username"]},
|
211
217
|
)
|
212
218
|
|
@@ -225,7 +231,7 @@ def register_auth(environment: locust.env.Environment):
|
|
225
231
|
|
226
232
|
@auth_blueprint.route("/confirm-signup", methods=["POST"])
|
227
233
|
def confirm_signup():
|
228
|
-
if not
|
234
|
+
if not environment.parsed_options.allow_signup:
|
229
235
|
return redirect(url_for("locust.login"))
|
230
236
|
|
231
237
|
session["auth_sign_up_error"] = ""
|
@@ -233,9 +239,10 @@ def register_auth(environment: locust.env.Environment):
|
|
233
239
|
|
234
240
|
try:
|
235
241
|
auth_response = requests.post(
|
236
|
-
f"{
|
242
|
+
f"{environment.parsed_options.deployer_url}/auth/confirm-signup",
|
237
243
|
json={
|
238
244
|
"username": session.get("username"),
|
245
|
+
"full_name": session.get("full_name"),
|
239
246
|
"user_sub": session["user_sub"],
|
240
247
|
"confirmation_code": confirmation_code,
|
241
248
|
},
|
@@ -247,7 +254,7 @@ def register_auth(environment: locust.env.Environment):
|
|
247
254
|
session["auth_info"] = "Account created successfully!"
|
248
255
|
session["auth_sign_up_error"] = ""
|
249
256
|
|
250
|
-
return redirect(
|
257
|
+
return redirect("https://docs.locust.cloud/")
|
251
258
|
except requests.exceptions.HTTPError as e:
|
252
259
|
message = e.response.json().get("Message", "An unexpected error occured. Please try again.")
|
253
260
|
session["auth_info"] = ""
|
locust_cloud/cloud.py
CHANGED
@@ -102,7 +102,7 @@ advanced.add_argument(
|
|
102
102
|
advanced.add_argument(
|
103
103
|
"--region",
|
104
104
|
type=str,
|
105
|
-
default=os.environ.get("AWS_DEFAULT_REGION")
|
105
|
+
default="eu-north-1", # temp setup for pycon # os.environ.get("AWS_DEFAULT_REGION")
|
106
106
|
help="Sets the AWS region to use for the deployed cluster, e.g. us-east-1. It defaults to use AWS_DEFAULT_REGION env var, like AWS tools.",
|
107
107
|
)
|
108
108
|
parser.add_argument(
|
@@ -148,6 +148,12 @@ parser.add_argument(
|
|
148
148
|
default="latest",
|
149
149
|
help=configargparse.SUPPRESS, # overrides the locust-cloud docker image tag. for internal use
|
150
150
|
)
|
151
|
+
parser.add_argument(
|
152
|
+
"--mock-server",
|
153
|
+
action="store_true",
|
154
|
+
default=False,
|
155
|
+
help=configargparse.SUPPRESS,
|
156
|
+
)
|
151
157
|
|
152
158
|
options, locust_options = parser.parse_known_args()
|
153
159
|
options: Namespace
|
@@ -203,17 +209,18 @@ def main() -> None:
|
|
203
209
|
aws_session_token = credentials.get("token", "")
|
204
210
|
|
205
211
|
if options.delete:
|
206
|
-
delete(
|
212
|
+
delete(credential_manager)
|
207
213
|
return
|
208
214
|
|
209
215
|
logger.info(f"Uploading {options.locustfile}")
|
210
216
|
logger.debug(f"... to {s3_bucket}")
|
211
217
|
s3 = credential_manager.session.client("s3")
|
212
218
|
try:
|
213
|
-
|
219
|
+
filename = options.username + "__" + os.path.basename(options.locustfile)
|
220
|
+
s3.upload_file(options.locustfile, s3_bucket, filename)
|
214
221
|
locustfile_url = s3.generate_presigned_url(
|
215
222
|
ClientMethod="get_object",
|
216
|
-
Params={"Bucket": s3_bucket, "Key":
|
223
|
+
Params={"Bucket": s3_bucket, "Key": filename},
|
217
224
|
ExpiresIn=3600,
|
218
225
|
)
|
219
226
|
logger.debug(f"Uploaded {options.locustfile} successfully")
|
@@ -228,10 +235,11 @@ def main() -> None:
|
|
228
235
|
if options.requirements:
|
229
236
|
logger.info(f"Uploading {options.requirements}")
|
230
237
|
try:
|
231
|
-
|
238
|
+
filename = options.username + "__" + "requirements.txt"
|
239
|
+
s3.upload_file(options.requirements, s3_bucket, filename)
|
232
240
|
requirements_url = s3.generate_presigned_url(
|
233
241
|
ClientMethod="get_object",
|
234
|
-
Params={"Bucket": s3_bucket, "Key":
|
242
|
+
Params={"Bucket": s3_bucket, "Key": filename},
|
235
243
|
ExpiresIn=3600,
|
236
244
|
)
|
237
245
|
logger.debug(f"Uploaded {options.requirements} successfully")
|
@@ -268,6 +276,7 @@ def main() -> None:
|
|
268
276
|
],
|
269
277
|
"user_count": options.users,
|
270
278
|
"image_tag": options.image_tag,
|
279
|
+
"mock_server": options.mock_server,
|
271
280
|
}
|
272
281
|
if options.workers is not None:
|
273
282
|
payload["worker_count"] = options.workers
|
@@ -377,10 +386,10 @@ def main() -> None:
|
|
377
386
|
logger.exception(e)
|
378
387
|
sys.exit(1)
|
379
388
|
finally:
|
380
|
-
delete(
|
389
|
+
delete(credential_manager)
|
381
390
|
|
382
391
|
|
383
|
-
def delete(
|
392
|
+
def delete(credential_manager):
|
384
393
|
try:
|
385
394
|
logger.info("Tearing down Locust cloud...")
|
386
395
|
credential_manager.refresh_credentials()
|
@@ -412,9 +421,9 @@ def delete(s3_bucket, credential_manager):
|
|
412
421
|
|
413
422
|
try:
|
414
423
|
logger.debug("Cleaning up locustfiles")
|
415
|
-
s3 = credential_manager.session.resource("s3")
|
416
|
-
bucket = s3.Bucket(s3_bucket)
|
417
|
-
bucket.objects.all().delete()
|
424
|
+
# s3 = credential_manager.session.resource("s3")
|
425
|
+
# bucket = s3.Bucket(s3_bucket)
|
426
|
+
# bucket.objects.all().delete()
|
418
427
|
except ClientError as e:
|
419
428
|
logger.debug(f"Failed to clean up locust files: {e}")
|
420
429
|
# sys.exit(1)
|
@@ -217,7 +217,7 @@ class Exporter:
|
|
217
217
|
cmd = sys.argv[1:]
|
218
218
|
with self.pool.connection() as conn:
|
219
219
|
conn.execute(
|
220
|
-
"INSERT INTO testruns (id, num_users, worker_count, username, locustfile,
|
220
|
+
"INSERT INTO testruns (id, num_users, worker_count, username, locustfile, arguments, customer) VALUES (%s,%s,%s,%s,%s,%s,current_user)",
|
221
221
|
(
|
222
222
|
self._run_id,
|
223
223
|
self.env.runner.target_user_count if self.env.runner else 1,
|
@@ -228,8 +228,7 @@ class Exporter:
|
|
228
228
|
)
|
229
229
|
else 0,
|
230
230
|
self.env.web_ui.template_args.get("username", "") if self.env.web_ui else "",
|
231
|
-
self.env.parsed_locustfiles[0].split("/")[-1],
|
232
|
-
self.env.parsed_options.description,
|
231
|
+
self.env.parsed_locustfiles[0].split("/")[-1].split("__")[-1],
|
233
232
|
" ".join(cmd),
|
234
233
|
),
|
235
234
|
)
|