arthexis 0.1.26__py3-none-any.whl → 0.1.28__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.
Potentially problematic release.
This version of arthexis might be problematic. Click here for more details.
- {arthexis-0.1.26.dist-info → arthexis-0.1.28.dist-info}/METADATA +16 -11
- {arthexis-0.1.26.dist-info → arthexis-0.1.28.dist-info}/RECORD +39 -38
- config/settings.py +7 -1
- config/settings_helpers.py +176 -1
- config/urls.py +18 -2
- core/admin.py +265 -23
- core/apps.py +6 -2
- core/celery_utils.py +73 -0
- core/models.py +307 -63
- core/system.py +17 -2
- core/tasks.py +304 -129
- core/test_system_info.py +43 -5
- core/tests.py +202 -2
- core/user_data.py +52 -19
- core/views.py +70 -3
- nodes/admin.py +348 -3
- nodes/apps.py +1 -1
- nodes/feature_checks.py +30 -0
- nodes/models.py +146 -18
- nodes/tasks.py +1 -1
- nodes/tests.py +181 -48
- nodes/views.py +148 -3
- ocpp/admin.py +1001 -10
- ocpp/consumers.py +572 -7
- ocpp/models.py +499 -33
- ocpp/store.py +406 -40
- ocpp/tasks.py +109 -145
- ocpp/test_rfid.py +73 -2
- ocpp/tests.py +982 -90
- ocpp/urls.py +5 -0
- ocpp/views.py +172 -70
- pages/context_processors.py +2 -0
- pages/models.py +9 -0
- pages/tests.py +166 -18
- pages/urls.py +1 -0
- pages/views.py +66 -3
- {arthexis-0.1.26.dist-info → arthexis-0.1.28.dist-info}/WHEEL +0 -0
- {arthexis-0.1.26.dist-info → arthexis-0.1.28.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.26.dist-info → arthexis-0.1.28.dist-info}/top_level.txt +0 -0
core/tasks.py
CHANGED
|
@@ -16,6 +16,20 @@ from django.utils import timezone
|
|
|
16
16
|
|
|
17
17
|
AUTO_UPGRADE_HEALTH_DELAY_SECONDS = 30
|
|
18
18
|
AUTO_UPGRADE_SKIP_LOCK_NAME = "auto_upgrade_skip_revisions.lck"
|
|
19
|
+
AUTO_UPGRADE_NETWORK_FAILURE_LOCK_NAME = "auto_upgrade_network_failures.lck"
|
|
20
|
+
AUTO_UPGRADE_NETWORK_FAILURE_THRESHOLD = 3
|
|
21
|
+
|
|
22
|
+
_NETWORK_FAILURE_PATTERNS = (
|
|
23
|
+
"could not resolve host",
|
|
24
|
+
"couldn't resolve host",
|
|
25
|
+
"failed to connect",
|
|
26
|
+
"connection timed out",
|
|
27
|
+
"network is unreachable",
|
|
28
|
+
"temporary failure in name resolution",
|
|
29
|
+
"name or service not known",
|
|
30
|
+
"could not resolve proxy",
|
|
31
|
+
"no route to host",
|
|
32
|
+
)
|
|
19
33
|
|
|
20
34
|
SEVERITY_NORMAL = "normal"
|
|
21
35
|
SEVERITY_LOW = "low"
|
|
@@ -128,11 +142,12 @@ def _read_remote_version(base_dir: Path, branch: str) -> str | None:
|
|
|
128
142
|
f"origin/{branch}:VERSION",
|
|
129
143
|
],
|
|
130
144
|
cwd=base_dir,
|
|
145
|
+
stderr=subprocess.STDOUT,
|
|
146
|
+
text=True,
|
|
131
147
|
)
|
|
132
|
-
.decode()
|
|
133
148
|
.strip()
|
|
134
149
|
)
|
|
135
|
-
except subprocess.CalledProcessError: # pragma: no cover - git failure
|
|
150
|
+
except (subprocess.CalledProcessError, FileNotFoundError): # pragma: no cover - git failure
|
|
136
151
|
return None
|
|
137
152
|
|
|
138
153
|
|
|
@@ -176,6 +191,141 @@ def _add_skipped_revision(base_dir: Path, revision: str) -> None:
|
|
|
176
191
|
)
|
|
177
192
|
|
|
178
193
|
|
|
194
|
+
def _network_failure_lock_path(base_dir: Path) -> Path:
|
|
195
|
+
return base_dir / "locks" / AUTO_UPGRADE_NETWORK_FAILURE_LOCK_NAME
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _read_network_failure_count(base_dir: Path) -> int:
|
|
199
|
+
lock_path = _network_failure_lock_path(base_dir)
|
|
200
|
+
try:
|
|
201
|
+
raw_value = lock_path.read_text(encoding="utf-8").strip()
|
|
202
|
+
except FileNotFoundError:
|
|
203
|
+
return 0
|
|
204
|
+
except OSError:
|
|
205
|
+
logger.warning("Failed to read auto-upgrade network failure lockfile")
|
|
206
|
+
return 0
|
|
207
|
+
if not raw_value:
|
|
208
|
+
return 0
|
|
209
|
+
try:
|
|
210
|
+
return int(raw_value)
|
|
211
|
+
except ValueError:
|
|
212
|
+
logger.warning(
|
|
213
|
+
"Invalid auto-upgrade network failure lockfile contents: %s", raw_value
|
|
214
|
+
)
|
|
215
|
+
return 0
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _write_network_failure_count(base_dir: Path, count: int) -> None:
|
|
219
|
+
lock_path = _network_failure_lock_path(base_dir)
|
|
220
|
+
try:
|
|
221
|
+
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
222
|
+
lock_path.write_text(str(count), encoding="utf-8")
|
|
223
|
+
except OSError:
|
|
224
|
+
logger.warning("Failed to update auto-upgrade network failure lockfile")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _reset_network_failure_count(base_dir: Path) -> None:
|
|
228
|
+
lock_path = _network_failure_lock_path(base_dir)
|
|
229
|
+
try:
|
|
230
|
+
if lock_path.exists():
|
|
231
|
+
lock_path.unlink()
|
|
232
|
+
except OSError:
|
|
233
|
+
logger.warning("Failed to remove auto-upgrade network failure lockfile")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _extract_error_output(exc: subprocess.CalledProcessError) -> str:
|
|
237
|
+
parts: list[str] = []
|
|
238
|
+
for attr in ("stderr", "stdout", "output"):
|
|
239
|
+
value = getattr(exc, attr, None)
|
|
240
|
+
if not value:
|
|
241
|
+
continue
|
|
242
|
+
if isinstance(value, bytes):
|
|
243
|
+
try:
|
|
244
|
+
value = value.decode()
|
|
245
|
+
except Exception: # pragma: no cover - best effort decoding
|
|
246
|
+
value = value.decode(errors="ignore")
|
|
247
|
+
parts.append(str(value))
|
|
248
|
+
detail = " ".join(part.strip() for part in parts if part)
|
|
249
|
+
if not detail:
|
|
250
|
+
detail = str(exc)
|
|
251
|
+
return detail
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _is_network_failure(exc: subprocess.CalledProcessError) -> bool:
|
|
255
|
+
command = exc.cmd
|
|
256
|
+
if isinstance(command, (list, tuple)):
|
|
257
|
+
if not command:
|
|
258
|
+
return False
|
|
259
|
+
first = str(command[0])
|
|
260
|
+
else:
|
|
261
|
+
command_str = str(command)
|
|
262
|
+
first = command_str.split()[0] if command_str else ""
|
|
263
|
+
if "git" not in first:
|
|
264
|
+
return False
|
|
265
|
+
detail = _extract_error_output(exc).lower()
|
|
266
|
+
return any(pattern in detail for pattern in _NETWORK_FAILURE_PATTERNS)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _record_network_failure(base_dir: Path, detail: str) -> int:
|
|
270
|
+
count = _read_network_failure_count(base_dir) + 1
|
|
271
|
+
_write_network_failure_count(base_dir, count)
|
|
272
|
+
_append_auto_upgrade_log(
|
|
273
|
+
base_dir,
|
|
274
|
+
f"Auto-upgrade network failure {count}: {detail}",
|
|
275
|
+
)
|
|
276
|
+
return count
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _charge_point_active(base_dir: Path) -> bool:
|
|
280
|
+
lock_path = base_dir / "locks" / "charging.lck"
|
|
281
|
+
if lock_path.exists():
|
|
282
|
+
return True
|
|
283
|
+
try:
|
|
284
|
+
from ocpp import store # type: ignore
|
|
285
|
+
except Exception:
|
|
286
|
+
return False
|
|
287
|
+
try:
|
|
288
|
+
connections = getattr(store, "connections", {})
|
|
289
|
+
except Exception: # pragma: no cover - defensive
|
|
290
|
+
return False
|
|
291
|
+
return bool(connections)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _trigger_auto_upgrade_reboot(base_dir: Path) -> None:
|
|
295
|
+
try:
|
|
296
|
+
subprocess.run(["sudo", "systemctl", "reboot"], check=False)
|
|
297
|
+
except Exception: # pragma: no cover - best effort reboot command
|
|
298
|
+
logger.exception(
|
|
299
|
+
"Failed to trigger reboot after repeated auto-upgrade network failures"
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _reboot_if_no_charge_point(base_dir: Path) -> None:
|
|
304
|
+
if _charge_point_active(base_dir):
|
|
305
|
+
_append_auto_upgrade_log(
|
|
306
|
+
base_dir,
|
|
307
|
+
"Skipping reboot after repeated auto-upgrade network failures; a charge point is active",
|
|
308
|
+
)
|
|
309
|
+
return
|
|
310
|
+
_append_auto_upgrade_log(
|
|
311
|
+
base_dir,
|
|
312
|
+
"Rebooting due to repeated auto-upgrade network failures",
|
|
313
|
+
)
|
|
314
|
+
_trigger_auto_upgrade_reboot(base_dir)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _handle_network_failure_if_applicable(
|
|
318
|
+
base_dir: Path, exc: subprocess.CalledProcessError
|
|
319
|
+
) -> bool:
|
|
320
|
+
if not _is_network_failure(exc):
|
|
321
|
+
return False
|
|
322
|
+
detail = _extract_error_output(exc)
|
|
323
|
+
failure_count = _record_network_failure(base_dir, detail)
|
|
324
|
+
if failure_count >= AUTO_UPGRADE_NETWORK_FAILURE_THRESHOLD:
|
|
325
|
+
_reboot_if_no_charge_point(base_dir)
|
|
326
|
+
return True
|
|
327
|
+
|
|
328
|
+
|
|
179
329
|
def _resolve_service_url(base_dir: Path) -> str:
|
|
180
330
|
"""Return the local URL used to probe the Django suite."""
|
|
181
331
|
|
|
@@ -189,7 +339,7 @@ def _resolve_service_url(base_dir: Path) -> str:
|
|
|
189
339
|
value = ""
|
|
190
340
|
if value:
|
|
191
341
|
mode = value.lower()
|
|
192
|
-
port =
|
|
342
|
+
port = 8888
|
|
193
343
|
return f"http://127.0.0.1:{port}/"
|
|
194
344
|
|
|
195
345
|
|
|
@@ -214,153 +364,178 @@ def check_github_updates() -> None:
|
|
|
214
364
|
base_dir = Path(__file__).resolve().parent.parent
|
|
215
365
|
mode_file = base_dir / "locks" / "auto_upgrade.lck"
|
|
216
366
|
mode = "version"
|
|
217
|
-
|
|
367
|
+
reset_network_failures = True
|
|
368
|
+
try:
|
|
369
|
+
if mode_file.exists():
|
|
370
|
+
try:
|
|
371
|
+
raw_mode = mode_file.read_text().strip()
|
|
372
|
+
except (OSError, UnicodeDecodeError):
|
|
373
|
+
logger.warning(
|
|
374
|
+
"Failed to read auto-upgrade mode lockfile", exc_info=True
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
cleaned_mode = raw_mode.lower()
|
|
378
|
+
if cleaned_mode:
|
|
379
|
+
mode = cleaned_mode
|
|
380
|
+
|
|
381
|
+
branch = "main"
|
|
218
382
|
try:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
383
|
+
subprocess.run(
|
|
384
|
+
["git", "fetch", "origin", branch],
|
|
385
|
+
cwd=base_dir,
|
|
386
|
+
check=True,
|
|
387
|
+
capture_output=True,
|
|
388
|
+
text=True,
|
|
223
389
|
)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
390
|
+
except subprocess.CalledProcessError as exc:
|
|
391
|
+
if _handle_network_failure_if_applicable(base_dir, exc):
|
|
392
|
+
reset_network_failures = False
|
|
393
|
+
raise
|
|
228
394
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
fh.write(
|
|
235
|
-
f"{timezone.now().isoformat()} check_github_updates triggered\n"
|
|
236
|
-
)
|
|
395
|
+
log_file = _auto_upgrade_log_path(base_dir)
|
|
396
|
+
with log_file.open("a") as fh:
|
|
397
|
+
fh.write(
|
|
398
|
+
f"{timezone.now().isoformat()} check_github_updates triggered\n"
|
|
399
|
+
)
|
|
237
400
|
|
|
238
|
-
notify = None
|
|
239
|
-
startup = None
|
|
240
|
-
try: # pragma: no cover - optional dependency
|
|
241
|
-
from core.notifications import notify # type: ignore
|
|
242
|
-
except Exception:
|
|
243
401
|
notify = None
|
|
244
|
-
try: # pragma: no cover - optional dependency
|
|
245
|
-
from nodes.apps import _startup_notification as startup # type: ignore
|
|
246
|
-
except Exception:
|
|
247
402
|
startup = None
|
|
403
|
+
try: # pragma: no cover - optional dependency
|
|
404
|
+
from core.notifications import notify # type: ignore
|
|
405
|
+
except Exception:
|
|
406
|
+
notify = None
|
|
407
|
+
try: # pragma: no cover - optional dependency
|
|
408
|
+
from nodes.apps import _startup_notification as startup # type: ignore
|
|
409
|
+
except Exception:
|
|
410
|
+
startup = None
|
|
248
411
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
)
|
|
262
|
-
if
|
|
263
|
-
startup()
|
|
264
|
-
return
|
|
265
|
-
|
|
266
|
-
remote_version = _read_remote_version(base_dir, branch)
|
|
267
|
-
local_version = _read_local_version(base_dir)
|
|
268
|
-
remote_severity = _resolve_release_severity(remote_version)
|
|
269
|
-
|
|
270
|
-
upgrade_stamp = timezone.now().strftime("@ %Y%m%d %H:%M")
|
|
271
|
-
|
|
272
|
-
upgrade_was_applied = False
|
|
273
|
-
|
|
274
|
-
if mode == "latest":
|
|
275
|
-
local_revision = (
|
|
276
|
-
subprocess.check_output(["git", "rev-parse", branch], cwd=base_dir)
|
|
277
|
-
.decode()
|
|
278
|
-
.strip()
|
|
279
|
-
)
|
|
280
|
-
if local_revision == remote_revision:
|
|
281
|
-
if startup:
|
|
282
|
-
startup()
|
|
283
|
-
return
|
|
284
|
-
|
|
285
|
-
if (
|
|
286
|
-
remote_version
|
|
287
|
-
and local_version
|
|
288
|
-
and remote_version != local_version
|
|
289
|
-
and remote_severity == SEVERITY_LOW
|
|
290
|
-
and _shares_stable_series(local_version, remote_version)
|
|
291
|
-
):
|
|
412
|
+
try:
|
|
413
|
+
remote_revision = subprocess.check_output(
|
|
414
|
+
["git", "rev-parse", f"origin/{branch}"],
|
|
415
|
+
cwd=base_dir,
|
|
416
|
+
stderr=subprocess.STDOUT,
|
|
417
|
+
text=True,
|
|
418
|
+
).strip()
|
|
419
|
+
except subprocess.CalledProcessError as exc:
|
|
420
|
+
if _handle_network_failure_if_applicable(base_dir, exc):
|
|
421
|
+
reset_network_failures = False
|
|
422
|
+
raise
|
|
423
|
+
|
|
424
|
+
skipped_revisions = _load_skipped_revisions(base_dir)
|
|
425
|
+
if remote_revision in skipped_revisions:
|
|
292
426
|
_append_auto_upgrade_log(
|
|
293
427
|
base_dir,
|
|
294
|
-
f"Skipping auto-upgrade for
|
|
428
|
+
f"Skipping auto-upgrade for blocked revision {remote_revision}",
|
|
295
429
|
)
|
|
296
430
|
if startup:
|
|
297
431
|
startup()
|
|
298
432
|
return
|
|
299
433
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
upgrade_was_applied = True
|
|
304
|
-
else:
|
|
305
|
-
local_value = local_version or "0"
|
|
306
|
-
remote_value = remote_version or local_value
|
|
434
|
+
remote_version = _read_remote_version(base_dir, branch)
|
|
435
|
+
local_version = _read_local_version(base_dir)
|
|
436
|
+
remote_severity = _resolve_release_severity(remote_version)
|
|
307
437
|
|
|
308
|
-
|
|
309
|
-
if startup:
|
|
310
|
-
startup()
|
|
311
|
-
return
|
|
438
|
+
upgrade_stamp = timezone.now().strftime("@ %Y%m%d %H:%M")
|
|
312
439
|
|
|
313
|
-
|
|
314
|
-
mode == "stable"
|
|
315
|
-
and local_version
|
|
316
|
-
and remote_version
|
|
317
|
-
and remote_version != local_version
|
|
318
|
-
and _shares_stable_series(local_version, remote_version)
|
|
319
|
-
and remote_severity != SEVERITY_CRITICAL
|
|
320
|
-
):
|
|
321
|
-
if startup:
|
|
322
|
-
startup()
|
|
323
|
-
return
|
|
440
|
+
upgrade_was_applied = False
|
|
324
441
|
|
|
325
|
-
if
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
442
|
+
if mode == "latest":
|
|
443
|
+
local_revision = (
|
|
444
|
+
subprocess.check_output(
|
|
445
|
+
["git", "rev-parse", branch],
|
|
446
|
+
cwd=base_dir,
|
|
447
|
+
stderr=subprocess.STDOUT,
|
|
448
|
+
text=True,
|
|
449
|
+
)
|
|
450
|
+
.strip()
|
|
451
|
+
)
|
|
452
|
+
if local_revision == remote_revision:
|
|
453
|
+
if startup:
|
|
454
|
+
startup()
|
|
455
|
+
return
|
|
456
|
+
|
|
457
|
+
if (
|
|
458
|
+
remote_version
|
|
459
|
+
and local_version
|
|
460
|
+
and remote_version != local_version
|
|
461
|
+
and remote_severity == SEVERITY_LOW
|
|
462
|
+
and _shares_stable_series(local_version, remote_version)
|
|
463
|
+
):
|
|
464
|
+
_append_auto_upgrade_log(
|
|
465
|
+
base_dir,
|
|
466
|
+
f"Skipping auto-upgrade for low severity patch {remote_version}",
|
|
467
|
+
)
|
|
468
|
+
if startup:
|
|
469
|
+
startup()
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
if notify:
|
|
473
|
+
notify("Upgrading...", upgrade_stamp)
|
|
474
|
+
args = ["./upgrade.sh", "--latest", "--no-restart"]
|
|
475
|
+
upgrade_was_applied = True
|
|
329
476
|
else:
|
|
330
|
-
|
|
331
|
-
|
|
477
|
+
local_value = local_version or "0"
|
|
478
|
+
remote_value = remote_version or local_value
|
|
479
|
+
|
|
480
|
+
if local_value == remote_value:
|
|
481
|
+
if startup:
|
|
482
|
+
startup()
|
|
483
|
+
return
|
|
484
|
+
|
|
485
|
+
if (
|
|
486
|
+
mode == "stable"
|
|
487
|
+
and local_version
|
|
488
|
+
and remote_version
|
|
489
|
+
and remote_version != local_version
|
|
490
|
+
and _shares_stable_series(local_version, remote_version)
|
|
491
|
+
and remote_severity != SEVERITY_CRITICAL
|
|
492
|
+
):
|
|
493
|
+
if startup:
|
|
494
|
+
startup()
|
|
495
|
+
return
|
|
496
|
+
|
|
497
|
+
if notify:
|
|
498
|
+
notify("Upgrading...", upgrade_stamp)
|
|
499
|
+
if mode == "stable":
|
|
500
|
+
args = ["./upgrade.sh", "--stable", "--no-restart"]
|
|
501
|
+
else:
|
|
502
|
+
args = ["./upgrade.sh", "--no-restart"]
|
|
503
|
+
upgrade_was_applied = True
|
|
332
504
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
505
|
+
with log_file.open("a") as fh:
|
|
506
|
+
fh.write(
|
|
507
|
+
f"{timezone.now().isoformat()} running: {' '.join(args)}\n"
|
|
508
|
+
)
|
|
337
509
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
service_file = base_dir / "locks/service.lck"
|
|
341
|
-
if service_file.exists():
|
|
342
|
-
service = service_file.read_text().strip()
|
|
343
|
-
subprocess.run(
|
|
344
|
-
[
|
|
345
|
-
"sudo",
|
|
346
|
-
"systemctl",
|
|
347
|
-
"kill",
|
|
348
|
-
"--signal=TERM",
|
|
349
|
-
service,
|
|
350
|
-
]
|
|
351
|
-
)
|
|
352
|
-
else:
|
|
353
|
-
subprocess.run(["pkill", "-f", "manage.py runserver"])
|
|
510
|
+
subprocess.run(args, cwd=base_dir, check=True)
|
|
354
511
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
512
|
+
service_file = base_dir / "locks/service.lck"
|
|
513
|
+
if service_file.exists():
|
|
514
|
+
service = service_file.read_text().strip()
|
|
515
|
+
subprocess.run(
|
|
516
|
+
[
|
|
517
|
+
"sudo",
|
|
518
|
+
"systemctl",
|
|
519
|
+
"kill",
|
|
520
|
+
"--signal=TERM",
|
|
521
|
+
service,
|
|
522
|
+
]
|
|
523
|
+
)
|
|
524
|
+
else:
|
|
525
|
+
subprocess.run(["pkill", "-f", "manage.py runserver"])
|
|
526
|
+
|
|
527
|
+
if upgrade_was_applied:
|
|
528
|
+
_append_auto_upgrade_log(
|
|
529
|
+
base_dir,
|
|
530
|
+
(
|
|
531
|
+
"Scheduled post-upgrade health check in %s seconds"
|
|
532
|
+
% AUTO_UPGRADE_HEALTH_DELAY_SECONDS
|
|
533
|
+
),
|
|
534
|
+
)
|
|
535
|
+
_schedule_health_check(1)
|
|
536
|
+
finally:
|
|
537
|
+
if reset_network_failures:
|
|
538
|
+
_reset_network_failure_count(base_dir)
|
|
364
539
|
|
|
365
540
|
|
|
366
541
|
@shared_task
|
core/test_system_info.py
CHANGED
|
@@ -64,13 +64,28 @@ class SystemInfoModeTests(SimpleTestCase):
|
|
|
64
64
|
try:
|
|
65
65
|
info = _gather_info()
|
|
66
66
|
self.assertEqual(info["mode"], "public")
|
|
67
|
-
self.assertEqual(info["port"],
|
|
67
|
+
self.assertEqual(info["port"], 8888)
|
|
68
68
|
finally:
|
|
69
69
|
lock_file.unlink()
|
|
70
70
|
if not any(lock_dir.iterdir()):
|
|
71
71
|
lock_dir.rmdir()
|
|
72
72
|
|
|
73
73
|
|
|
74
|
+
class SystemInfoPortLockTests(SimpleTestCase):
|
|
75
|
+
def test_uses_backend_port_lock_when_present(self):
|
|
76
|
+
lock_dir = Path(settings.BASE_DIR) / "locks"
|
|
77
|
+
lock_dir.mkdir(exist_ok=True)
|
|
78
|
+
port_file = lock_dir / "backend_port.lck"
|
|
79
|
+
port_file.write_text("9010", encoding="utf-8")
|
|
80
|
+
try:
|
|
81
|
+
info = _gather_info()
|
|
82
|
+
self.assertEqual(info["port"], 9010)
|
|
83
|
+
finally:
|
|
84
|
+
port_file.unlink()
|
|
85
|
+
if not any(lock_dir.iterdir()):
|
|
86
|
+
lock_dir.rmdir()
|
|
87
|
+
|
|
88
|
+
|
|
74
89
|
class SystemInfoRevisionTests(SimpleTestCase):
|
|
75
90
|
@patch("core.system.revision.get_revision", return_value="abcdef1234567890")
|
|
76
91
|
def test_includes_full_revision(self, mock_revision):
|
|
@@ -146,21 +161,44 @@ class SystemInfoRunserverDetectionTests(SimpleTestCase):
|
|
|
146
161
|
mock_run.return_value = CompletedProcess(
|
|
147
162
|
args=["pgrep"],
|
|
148
163
|
returncode=0,
|
|
149
|
-
stdout="123 python manage.py runserver 0.0.0.0:
|
|
164
|
+
stdout="123 python manage.py runserver 0.0.0.0:8888 --noreload\n",
|
|
150
165
|
)
|
|
151
166
|
|
|
152
167
|
info = _gather_info()
|
|
153
168
|
|
|
154
169
|
self.assertTrue(info["running"])
|
|
155
|
-
self.assertEqual(info["port"],
|
|
170
|
+
self.assertEqual(info["port"], 8888)
|
|
156
171
|
|
|
157
|
-
@patch("core.system._probe_ports", return_value=(True,
|
|
172
|
+
@patch("core.system._probe_ports", return_value=(True, 8888))
|
|
158
173
|
@patch("core.system.subprocess.run", side_effect=FileNotFoundError)
|
|
159
174
|
def test_falls_back_to_port_probe_when_pgrep_missing(self, mock_run, mock_probe):
|
|
160
175
|
info = _gather_info()
|
|
161
176
|
|
|
162
177
|
self.assertTrue(info["running"])
|
|
163
|
-
self.assertEqual(info["port"],
|
|
178
|
+
self.assertEqual(info["port"], 8888)
|
|
179
|
+
|
|
180
|
+
@patch("core.system._probe_ports", return_value=(False, None))
|
|
181
|
+
@patch("core.system.subprocess.run")
|
|
182
|
+
def test_runserver_fallbacks_to_backend_port_lock(self, mock_run, mock_probe):
|
|
183
|
+
lock_dir = Path(settings.BASE_DIR) / "locks"
|
|
184
|
+
lock_dir.mkdir(exist_ok=True)
|
|
185
|
+
port_file = lock_dir / "backend_port.lck"
|
|
186
|
+
port_file.write_text("9042", encoding="utf-8")
|
|
187
|
+
mock_run.return_value = CompletedProcess(
|
|
188
|
+
args=["pgrep"],
|
|
189
|
+
returncode=0,
|
|
190
|
+
stdout="123 python manage.py runserver --noreload\n",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
info = _gather_info()
|
|
195
|
+
finally:
|
|
196
|
+
port_file.unlink()
|
|
197
|
+
if not any(lock_dir.iterdir()):
|
|
198
|
+
lock_dir.rmdir()
|
|
199
|
+
|
|
200
|
+
self.assertTrue(info["running"])
|
|
201
|
+
self.assertEqual(info["port"], 9042)
|
|
164
202
|
|
|
165
203
|
|
|
166
204
|
class SystemSigilValueTests(SimpleTestCase):
|