88 lines
4.8 KiB
Python
88 lines
4.8 KiB
Python
"""Closed public fixture diagnostics. Never serialize exception text or SQL values."""
|
|
import json
|
|
import re
|
|
|
|
DRIVER_STAGES = ('session_registered', 'anchor_launch', 'anchor_containment', 'postgres_private_import',
|
|
'postgres_initdb', 'postgres_launch', 'anchor_identity', 'helper_launch', 'full_containment',
|
|
'helper_interpreter', 'candidate_import', 'bootstrap', 'restart', 'concurrency', 'fork', 'lost-ack',
|
|
'restored', 'database_dump', 'database_restore', 'cleanup', 'complete')
|
|
PROBE_STAGES = ('probe_entry', 'loopback_check', 'phase_dispatch', 'bootstrap_connect_wait',
|
|
'bootstrap_connected', 'bootstrap_schema', 'bootstrap_migration', 'bootstrap_epoch_insert',
|
|
'bootstrap_role_create', 'bootstrap_runtime_grants', 'bootstrap_checkpoint_init',
|
|
'bootstrap_directory_init', 'bootstrap_ensure', 'bootstrap_ensure_readback',
|
|
'bootstrap_runtime_ddl_refusal', 'bootstrap_complete', 'restart_load_snapshot',
|
|
'restart_reopen_store', 'restart_replay_ensure', 'restart_revoke', 'restart_revoke_readback',
|
|
'restart_prestate_equality', 'restart_foreign_context', 'restart_complete', 'phase_complete')
|
|
ERROR_CLASSES = ('GroupProvenanceUnavailable', 'RuntimeError', 'ValueError', 'TypeError', 'KeyError',
|
|
'FileNotFoundError', 'PermissionError', 'OperationalError', 'ProgrammingError', 'IntegrityError',
|
|
'InsufficientPrivilege', 'InvalidPassword', 'UndefinedTable', 'UndefinedColumn', 'SyntaxError',
|
|
'DuplicateTable', 'DuplicateObject', 'InvalidSchemaName', 'DeadlockDetected', 'ConnectionException',
|
|
'InvalidAuthorizationSpecification', 'OSError', 'TimeoutExpired', 'ImportError',
|
|
'ModuleNotFoundError', 'JSONDecodeError', 'OtherError')
|
|
|
|
|
|
def error_metadata(error, stage):
|
|
if stage not in DRIVER_STAGES + PROBE_STAGES:
|
|
stage = 'phase_dispatch'
|
|
name = type(error).__name__
|
|
name = name if name in ERROR_CLASSES else 'OtherError'
|
|
state = None
|
|
current = error
|
|
for _ in range(8):
|
|
if current is None:
|
|
break
|
|
try:
|
|
candidate = getattr(current, 'sqlstate', None)
|
|
if isinstance(candidate, str) and re.fullmatch('[0-9A-Z]{5}', candidate):
|
|
state = candidate
|
|
break
|
|
current = current.__cause__ or current.__context__
|
|
except Exception:
|
|
break
|
|
return {'stage': stage, 'exception_class': name, 'sqlstate': state}
|
|
|
|
|
|
def emit_stage(stage):
|
|
if stage not in PROBE_STAGES:
|
|
raise RuntimeError('invalid_public_fixture_stage')
|
|
print(json.dumps({'event': 'stage', 'stage': stage}), flush=True)
|
|
|
|
|
|
def parse_frames(raw, *, require_final=True):
|
|
if isinstance(raw, bytes):
|
|
raw = raw.decode('utf-8')
|
|
if not isinstance(raw, str) or len(raw) > 32768:
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
stages, final = [], None
|
|
lines = raw.splitlines()
|
|
if len(lines) > 96:
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
for line in lines:
|
|
value = json.loads(line)
|
|
if not isinstance(value, dict) or final is not None:
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
if value.get('event') == 'stage':
|
|
if set(value) != {'event', 'stage'} or value['stage'] not in PROBE_STAGES:
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
stages.append(value['stage'])
|
|
elif value.get('status') in {'passed', 'failed'} and final is None:
|
|
if value['status'] == 'failed':
|
|
if set(value) != {'status', 'failure', 'diagnostic'} or value['failure'] != 'synthetic_pg_oracle_failed':
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
diagnostic = value['diagnostic']
|
|
if set(diagnostic) != {'stage', 'exception_class', 'sqlstate'} or diagnostic['stage'] not in PROBE_STAGES or diagnostic['exception_class'] not in ERROR_CLASSES:
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
if diagnostic['sqlstate'] is not None and not re.fullmatch('[0-9A-Z]{5}', str(diagnostic['sqlstate'])):
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
elif (set(value) != {'status', 'checks', 'psycopg_version', 'active_database'}
|
|
or not isinstance(value['checks'], dict)
|
|
or not isinstance(value['psycopg_version'], str)
|
|
or not re.fullmatch(r'3\.[0-9]+\.[0-9]+(?:[a-z]+[0-9]+)?', value['psycopg_version'])
|
|
or value['active_database'] not in {'postgres', 'fixture_fork'}):
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
final = value
|
|
else:
|
|
raise RuntimeError('invalid_public_fixture_diagnostics')
|
|
if require_final and final is None:
|
|
raise RuntimeError('missing_public_fixture_result')
|
|
return stages, final
|