Skip to content

Commit c37d30e

Browse files
Improve deployment stability by adding health check endpoints []
This allows tests to know when services are ready/pauses "docker compose up"'s return until services are actually listening. This also refactors the deployment to use Quart for the servers (consistent with the AcknowlEDG API server) so that developers do not need to maintain knowledge of multiple WebSocket server patterns. Additionally, management of process pool jobs has been improved so that a task taking longer than they are allowed, responding to a review no longer being monitored, or similar, are properly cleaned up without negatively impacting other tasks. In a similar "reliability" vein, caps have been added to ensure input data stays within reasonable bounds to preserve service health. Finally, a number of cleanup passes have been taken to improve code quality.
1 parent f6825fd commit c37d30e

53 files changed

Lines changed: 4470 additions & 1444 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/skills/acknowledg-client/SKILL.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ Auth header: `X-AcknowlEDG-API-Key` (never put the key in query strings).
8181
`RoleOccupied`: discard that handler / stop reconnecting that section; keep
8282
other sections running.
8383

84+
Batch subscribe / unsubscribe answers are not timed: the server answers at
85+
once, and a lost connection (keepalive pings notice) fails what is pending
86+
as `NotConnectedError`; `add_section` keeps the section registered, and the
87+
next session restores it.
88+
89+
`IncompatibleProtocolVersion` carries the peer's `revision` (the server's
90+
`outdated-proto` says its own) and `peer_is_newer`. It stops a session;
91+
`BatchClient(wait_for_server_update=True)` instead waits out an older
92+
server, reconnecting every `SERVER_UPDATE_RETRY_S` (5 s) with its sections
93+
still registered. A newer server still stops it: the client must be updated.
94+
8495
## Batch subscription maps
8596

8697
`_BatchClientReview` tracks:

‎dev_tools/bin/edgy‎

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,15 +255,43 @@ def load_edgy_config(args: argparse.Namespace, test_home: Path) -> EdgyConfig:
255255
)
256256

257257
test_suites = config_file_dict.get('test_suites', {})
258+
primary_test_suites = test_suites.get('primary', [])
259+
secondary_test_suites = test_suites.get('secondary', [])
260+
_validate_configured_suites(
261+
config_file_path,
262+
{'primary': primary_test_suites, 'secondary': secondary_test_suites}
263+
)
258264
config_sets = config_file_dict.get('config_sets', {})
259265
return EdgyConfig(
260266
test_home,
261267
loaded_configs,
262268
config_sets,
263-
test_suites.get('primary', []),
264-
test_suites.get('secondary', [])
269+
primary_test_suites,
270+
secondary_test_suites
265271
)
266272

273+
def _validate_configured_suites(config_file_path: Path,
274+
test_suites: Dict[str, Any]) -> None:
275+
'''Exit unless each list of suites ``config_file_path`` gives is a list of
276+
valid names.'''
277+
invalid_test_suite_encountered = False
278+
for kind, test_suite_names in test_suites.items():
279+
if not isinstance(test_suite_names, list):
280+
edgutil.eprint(f"Invalid {kind} test suites {test_suite_names!r} in "
281+
f"{config_file_path} (expected a list of names).")
282+
invalid_test_suite_encountered = True
283+
continue
284+
for test_suite in test_suite_names:
285+
if not (isinstance(test_suite, str) and
286+
edgtest.is_valid_suite_name(test_suite)):
287+
edgutil.eprint(f"Invalid test suite name {test_suite!r} in "
288+
f"{config_file_path} (expected 1-64 of A-Z, a-z, "
289+
"0-9, _ and -).")
290+
invalid_test_suite_encountered = True
291+
292+
if invalid_test_suite_encountered:
293+
sys.exit(1)
294+
267295
def get_expectation_path(run_config: TestRunConfig,
268296
test_suite_name: str) -> Path:
269297
return Path(run_config.expectations_dir, f"{test_suite_name}.log")
@@ -627,7 +655,7 @@ def _diagnose_bad_expectations_file(file_path: Path) -> None:
627655
line_ctr += 1
628656
try:
629657
parse_test_status(line_text)
630-
except:
658+
except Exception:
631659
if len(line_text) != 0:
632660
edgutil.eprint(
633661
f"{file_path_str}, line {line_ctr}: "
@@ -668,7 +696,7 @@ def _construct_replay_filters(
668696
)
669697
for key in comparison:
670698
desired_tests.add(parsed_test_status_key_to_name(key))
671-
except:
699+
except Exception:
672700
edgutil.eprint(
673701
f"Failed to compare the previous run of the {test_suite.name} "
674702
'suite to its expectations.'
@@ -691,7 +719,7 @@ def _construct_replay_filters(
691719
elif (args.replay == 'output-mismatch' and
692720
parsed_test_status.is_output_mismatch()):
693721
desired_tests.add(parsed_test_status_key_to_name(key))
694-
except:
722+
except Exception:
695723
edgutil.eprint(
696724
f"Failed to parse test statuses in previous run of {test_suite.name}."
697725
)
@@ -1246,7 +1274,7 @@ class TestStatusChecker:
12461274
encountered_lines: List[str]) -> Optional['TestStatusChecker']:
12471275
try:
12481276
return TestStatusChecker(expected_lines, encountered_lines)
1249-
except:
1277+
except Exception:
12501278
return None
12511279

12521280
def _get_status_for_comparison(

‎dev_tools/bin/ifc-qt‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ import edgifc.decoration
2828
from enum import IntEnum
2929
from pathlib import Path
3030

31-
from PySide6 import QtCore, QtWidgets, QtGui # type: ignore[import-not-found,import]
31+
from PySide6 import ( # type: ignore[import-not-found,import]
32+
QtCore,
33+
QtWidgets,
34+
QtGui
35+
)
3236

3337
from edgifc import (
3438
IFCReader,
@@ -383,7 +387,7 @@ class PartitionNodeFormatter:
383387
values.append(template_string.format(value.description))
384388
else:
385389
values.append(format_fn(value))
386-
except:
390+
except Exception:
387391
traceback.print_exc()
388392
values.append(edgifc.decoration.UNKNOWN_VALUE)
389393

@@ -428,7 +432,7 @@ class PartitionNodeFormatter:
428432
stream_reader,
429433
tacit_field.name()
430434
)
431-
except:
435+
except Exception:
432436
traceback.print_exc()
433437
field_value = 0
434438

‎dev_tools/pylibs/edgacknowledg/__init__.py‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def _parse_envelope(response_message: DecodableMessage) -> ProtocolMessage:
330330
revision = response_json.get('r')
331331

332332
if revision != PROTOCOL_REVISION:
333-
raise IncompatibleProtocolVersion()
333+
raise IncompatibleProtocolVersion(revision)
334334
return response_json
335335

336336
def decode_response(
@@ -347,7 +347,24 @@ def decode_response(
347347
return (tag, value, response_json.get('s'))
348348

349349
class IncompatibleProtocolVersion(Exception):
350-
pass
350+
'''The peer speaks another protocol revision: ``revision``, as it said
351+
(the server's ``outdated-proto`` carries its own), if it said.
352+
'''
353+
354+
def __init__(self, revision: Any = None) -> None:
355+
self.revision = revision
356+
super().__init__(
357+
f"the peer speaks protocol revision {revision!r}, not "
358+
f"{PROTOCOL_REVISION}"
359+
)
360+
361+
@property
362+
def peer_is_newer(self) -> bool:
363+
'''Whether the peer is newer (this side must be updated), rather than
364+
older, or saying no revision it could be compared by.
365+
'''
366+
return (isinstance(self.revision, int) and
367+
self.revision > PROTOCOL_REVISION)
351368

352369
class UnexpectedResponseTag(Exception):
353370
pass

0 commit comments

Comments
 (0)