669 lines
36 KiB
Python
669 lines
36 KiB
Python
"""Real PG16 regression; run only in the trusted, offline disposable test image.
|
|
|
|
Main provisions a fresh truf-projection-loss-test-<32 hex> volume separately.
|
|
Run python -u -I -S -B /opt/truf/tests/container_projection_recovery_e2e.py
|
|
--budget-seconds 300. The budget is cooperative: it NEVER kills PostgreSQL or
|
|
releases uncertain lifecycle authority. A FAILED_HOLD may outlive that budget.
|
|
|
|
The actual schema/migration helpers, queries, transactions and locks are used.
|
|
Only the recovery module's manifest pin is substituted in memory for this fresh
|
|
fixture. Fault adapters raise only AFTER real SQL execution or real commit.
|
|
No accepted journal is deleted, no old output is rebuilt, and no application
|
|
initialized marker, supervisor, scanner, ingester or provider is started.
|
|
"""
|
|
|
|
import argparse
|
|
import contextlib
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import runpy
|
|
import signal
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from types import SimpleNamespace
|
|
|
|
|
|
OUTPUT = sys.stdout
|
|
STOPPED = False
|
|
IMAGE_PATH = Path('/opt/truf/tests/container_projection_recovery_e2e.py')
|
|
STAMP = '2026-09-16T00:00:00+00:00'
|
|
APPENDS = 38024
|
|
OLD_OFFSET = 97783145
|
|
|
|
|
|
def require(value, check):
|
|
if not value:
|
|
raise AssertionError(check)
|
|
|
|
|
|
def emit(**values):
|
|
try:
|
|
print(json.dumps(values, sort_keys=True), file=OUTPUT, flush=True)
|
|
except BaseException:
|
|
pass
|
|
|
|
|
|
def safe_error(error):
|
|
try:
|
|
line, state, current = 0, None, error
|
|
for _ in range(8):
|
|
tb = current.__traceback__
|
|
while tb is not None:
|
|
if tb.tb_frame.f_code.co_filename == str(IMAGE_PATH):
|
|
line = tb.tb_lineno
|
|
tb = tb.tb_next
|
|
candidate = getattr(current, 'sqlstate', None)
|
|
if state is None and isinstance(candidate, str) and re.fullmatch(r'[A-Z0-9]{5}', candidate):
|
|
state = candidate
|
|
current = current.__cause__ or current.__context__
|
|
if current is None:
|
|
break
|
|
name = type(error).__name__
|
|
if not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]{0,63}', name or ''):
|
|
name = 'other'
|
|
return {'error_type': next((i for i, kind in enumerate(
|
|
(TimeoutError, OSError, ValueError, RuntimeError, KeyboardInterrupt, AssertionError,
|
|
TypeError, KeyError, AttributeError, ImportError, LookupError), 1)
|
|
if isinstance(error, kind)), 0), 'line': line, 'sqlstate': state, 'error_class': name}
|
|
except BaseException:
|
|
return {'error_type': 0, 'line': 0, 'sqlstate': None, 'error_class': 'other'}
|
|
|
|
|
|
def hold_pause():
|
|
try:
|
|
time.sleep(2)
|
|
except BaseException:
|
|
pass
|
|
|
|
|
|
def stop_confirmed(backend, pg):
|
|
"""Retain this exact backend and the caller's two locks until positive stop."""
|
|
global STOPPED
|
|
attempts = 0
|
|
while True:
|
|
try:
|
|
attempts += 1
|
|
result = backend.stop()
|
|
require(result.completed is True and result.stopped is True, 'stop_result')
|
|
require(backend.probe().kind == pg.ProbeKind.STOPPED, 'stopped_probe')
|
|
backend.close()
|
|
STOPPED = True
|
|
emit(stage='stop', stopped=True, attempts=attempts)
|
|
return
|
|
except BaseException as error:
|
|
emit(stage='stop', failed_hold=True, attempts=attempts, **safe_error(error))
|
|
hold_pause()
|
|
|
|
|
|
def initialize_empty(runtime, config_path):
|
|
"""The real lifecycle child owns initialization compensation; never kill it."""
|
|
try:
|
|
child = subprocess.Popen(runtime._bootstrap_command(
|
|
'postgres-runtime', 'initialize-empty', '--config', str(config_path)),
|
|
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except BaseException as error:
|
|
emit(stage='initialize_empty', failed_hold=True, **safe_error(error))
|
|
return None
|
|
attempts = 0
|
|
while True:
|
|
try:
|
|
code = child.wait(timeout=10)
|
|
emit(stage='initialize_empty', completed=code == 0, exit_code=code)
|
|
return code
|
|
except BaseException as error:
|
|
attempts += 1
|
|
emit(stage='initialize_empty', failed_hold=True, attempts=attempts, **safe_error(error))
|
|
hold_pause()
|
|
|
|
|
|
def checkpoint(runtime, deadline):
|
|
require(runtime._shutdown_requested is False, 'shutdown_requested')
|
|
if time.monotonic() >= deadline:
|
|
raise TimeoutError('driver_budget')
|
|
|
|
|
|
def identifier(name):
|
|
require(isinstance(name, str) and re.fullmatch(r'[a-z_][a-z0-9_]*', name), 'fixture_identifier')
|
|
return '"' + name + '"'
|
|
|
|
|
|
def digest(payload):
|
|
return hashlib.sha256(payload).hexdigest()
|
|
|
|
|
|
def metadata(connection):
|
|
row = connection.execute("""SELECT pg_catalog.row_to_json(s) AS stream,
|
|
pg_catalog.row_to_json(c) AS cursor FROM public.projection_streams s
|
|
JOIN public.projection_cursors c USING (stream_name)
|
|
WHERE s.stream_name = 'found_secrets'""").fetchone()
|
|
parsed = {}
|
|
for key in ('stream', 'cursor'):
|
|
value = row[key]
|
|
if isinstance(value, str):
|
|
value = json.loads(value)
|
|
require(isinstance(value, dict), 'metadata_object')
|
|
parsed[key] = value
|
|
return parsed
|
|
|
|
|
|
def proof(connection, check):
|
|
"""Independent, bounded-fixture whole-row digests; never return raw rows."""
|
|
tables = connection.execute("""SELECT c.relname AS name FROM pg_catalog.pg_class c
|
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE n.nspname = 'public' AND c.relkind = 'r' ORDER BY c.relname""").fetchall()
|
|
result = {'tables': {}, 'sequences': {'public': {}}}
|
|
for table in tables:
|
|
check()
|
|
name = table['name']
|
|
where = " WHERE t.stream_name <> 'found_secrets'" if name in ('projection_streams', 'projection_cursors') else ''
|
|
row = connection.execute("""SELECT count(*) AS rows, pg_catalog.encode(pg_catalog.sha256(
|
|
pg_catalog.convert_to(COALESCE(string_agg(h, '' ORDER BY h), ''), 'UTF8')), 'hex') AS sha256
|
|
FROM (SELECT pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(
|
|
pg_catalog.row_to_json(t)::text, 'UTF8')), 'hex') COLLATE "C" AS h FROM public."""
|
|
+ identifier(name) + ' AS t' + where + ') AS hashes').fetchone()
|
|
require(0 <= row['rows'] <= 100000, 'bounded_fixture')
|
|
result['tables'][name] = row
|
|
sequences = connection.execute("""SELECT c.relname AS name FROM pg_catalog.pg_class c
|
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE n.nspname = 'public' AND c.relkind = 'S' ORDER BY c.relname""").fetchall()
|
|
for row in sequences:
|
|
result['sequences']['public'][row['name']] = connection.execute(
|
|
'SELECT last_value, is_called FROM public.' + identifier(row['name'])).fetchone()
|
|
return result
|
|
|
|
|
|
def journal_snapshot(runtime, recovery):
|
|
from container_import import _input, _json
|
|
|
|
path = runtime.DATA / 'config' / recovery.JOURNAL_NAME
|
|
with _input(path, runtime) as (handle, before):
|
|
require(0 < before[4] <= recovery.MAX_JOURNAL, 'journal_bound')
|
|
raw = handle.read(recovery.MAX_JOURNAL + 1)
|
|
value = _json(raw)
|
|
require(raw == recovery._encoded(value) and value['record']['state'] == 'PREPARED'
|
|
and value['sha256'] == digest(recovery._encoded(value['record'])), 'durable_journal')
|
|
return raw, before, value
|
|
|
|
|
|
class ObservedConnection:
|
|
"""Delegate everything to psycopg; inject only after a real operation."""
|
|
def __init__(self, connection, runtime, recovery, check, *, after_update=False, lost_ack=False):
|
|
self.connection, self.runtime, self.recovery, self.check = connection, runtime, recovery, check
|
|
self.after_update, self.lost_ack = after_update, lost_ack
|
|
self.updates, self.commits, self.injected = 0, 0, False
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self.connection, name)
|
|
|
|
def execute(self, query, *args, **kwargs):
|
|
result = self.connection.execute(query, *args, **kwargs)
|
|
sql = ' '.join(query.split()).upper() if isinstance(query, str) else ''
|
|
if sql.startswith('UPDATE '):
|
|
self.updates += 1
|
|
if self.after_update and sql.startswith('UPDATE PUBLIC.PROJECTION_STREAMS '):
|
|
require(result.rowcount == 1, 'real_first_update')
|
|
journal_snapshot(self.runtime, self.recovery)
|
|
self.after_update, self.injected = False, True
|
|
raise OSError('synthetic_transport_after_real_update')
|
|
self.check()
|
|
return result
|
|
|
|
@contextlib.contextmanager
|
|
def transaction(self, *args, **kwargs):
|
|
with self.connection.transaction(*args, **kwargs) as transaction:
|
|
yield transaction
|
|
self.commits += 1
|
|
if self.lost_ack:
|
|
self.lost_ack, self.injected = False, True
|
|
raise OSError('synthetic_ack_loss_after_real_commit')
|
|
|
|
|
|
def seed_history(connection, runtime, importer):
|
|
"""Seed real production tables; historical byte proofs need no old files."""
|
|
with connection.transaction():
|
|
connection.execute("""INSERT INTO public.target_scans(
|
|
id, scan_event_id, scan_event_hash, source, target, normalized_target, scan_type,
|
|
status, ended_at, findings_count, raw_result_storage, created_at)
|
|
SELECT n, lpad(to_hex(n), 32, '0'), encode(sha256(convert_to('event-' || n, 'UTF8')), 'hex'),
|
|
'fixture', 'fixture:' || n, 'fixture:' || n, 'fixture', 'found', %s, 1, 'normalized_v2', %s
|
|
FROM generate_series(1, 38024) AS g(n)""", (STAMP, STAMP))
|
|
connection.execute("""INSERT INTO public.findings(
|
|
id, target_scan_id, source, target, detector_name, detector_type, verified,
|
|
raw_secret, redacted_secret, secret_hash, finding_uid, created_at)
|
|
SELECT id, id, 'fixture', target, 'Fixture', 'fixture', 0,
|
|
'synthetic-only-' || id, 'fixture', encode(sha256(convert_to('synthetic-only-' || id, 'UTF8')), 'hex'),
|
|
encode(sha256(convert_to('finding-' || id, 'UTF8')), 'hex'), %s FROM public.target_scans""", (STAMP,))
|
|
connection.execute("""INSERT INTO public.scan_result_compat(
|
|
target_scan_id, schema_version, metadata_json, metadata_sha256, metadata_bytes, reconstruction_status, created_at)
|
|
SELECT id, 2, '{}', encode(sha256(convert_to('{}', 'UTF8')), 'hex'), 2, 'exact', %s
|
|
FROM public.target_scans""", (STAMP,))
|
|
connection.execute("""INSERT INTO public.finding_compat_payloads(
|
|
finding_id, raw_value, extension_json, payload_sha256, payload_bytes, payload_omitted, created_at)
|
|
SELECT id, raw_secret, '{}', encode(sha256(convert_to(raw_secret, 'UTF8')), 'hex'),
|
|
octet_length(raw_secret), 0, %s FROM public.findings""", (STAMP,))
|
|
connection.execute("""INSERT INTO public.projection_jobs(
|
|
id, job_kind, event_id, event_hash, target_scan_id, status, required_stream_mask,
|
|
capacity_items, capacity_bytes, capacity_released, attempts, created_at, updated_at, completed_at)
|
|
SELECT id, 'scan_event', scan_event_id, scan_event_hash, id, 'completed', 2,
|
|
1, 65536, 1, 1, %s, %s, %s FROM public.target_scans""", (STAMP, STAMP, STAMP))
|
|
connection.execute("""WITH positions AS (
|
|
SELECT id, event_id, event_hash, (id - 1) / 2716 AS generation,
|
|
CASE WHEN id > 35308 THEN 97783145::bigint ELSE 134217728::bigint END AS bytes,
|
|
(id - 1) %% 2716 AS ordinal FROM public.projection_jobs)
|
|
INSERT INTO public.projection_appends(id, job_id, stream_name, event_id, event_hash,
|
|
generation, byte_offset, byte_length, payload_sha256, record_count, state, prepared_at, appended_at)
|
|
SELECT id, id, 'found_secrets', event_id, event_hash, generation,
|
|
bytes * ordinal / 2716, bytes * (ordinal + 1) / 2716 - bytes * ordinal / 2716,
|
|
encode(sha256(convert_to('historical-output-' || id, 'UTF8')), 'hex'),
|
|
1, 'appended', %s, %s FROM positions""", (STAMP, STAMP))
|
|
connection.execute("""INSERT INTO public.projection_rotations(
|
|
id, stream_name, from_generation, to_generation, source_bytes, segment_relative_path, state, created_at, completed_at)
|
|
SELECT n + 1, 'found_secrets', n, n + 1, 134217728,
|
|
'found_secrets.g' || lpad(n::text, 6, '0') || '.jsonl', 'completed', %s, %s
|
|
FROM generate_series(0, 12) AS g(n)""", (STAMP, STAMP))
|
|
for i in range(18):
|
|
provider = f'p{i:02d}'
|
|
for suffix, filename in (('results', 'Results.jsonl'), ('status', 'Checked.txt')):
|
|
if suffix == 'status' and i >= 13:
|
|
continue
|
|
name = f'keycheck:{provider}:{suffix}'
|
|
connection.execute("""INSERT INTO public.projection_streams(
|
|
stream_name, base_relative_path, current_generation, rotation_bytes, max_generations, created_at, updated_at)
|
|
VALUES (%s, %s, 0, 33554432, 16, %s, %s)""", (name, f'{provider}/{provider}{filename}', STAMP, STAMP))
|
|
connection.execute("""INSERT INTO public.projection_cursors(stream_name, generation, committed_offset, updated_at)
|
|
VALUES (%s, 0, %s, %s)""", (name, 1000 + i if suffix == 'status' else 0, STAMP))
|
|
connection.execute("UPDATE public.projection_streams SET current_generation = 13 WHERE stream_name = 'found_secrets'")
|
|
connection.execute("""UPDATE public.projection_cursors SET generation = 13, committed_offset = 97783145,
|
|
last_append_id = 38024, last_job_id = 38024,
|
|
last_event_id = (SELECT event_id FROM public.projection_jobs WHERE id = 38024),
|
|
last_event_hash = (SELECT event_hash FROM public.projection_jobs WHERE id = 38024)
|
|
WHERE stream_name = 'found_secrets'""")
|
|
for worker in ('result_ingester', 'jsonl_projector'):
|
|
connection.execute("""INSERT INTO public.pipeline_leases(worker_name, generation, lease_token,
|
|
supervisor_instance_id, owner_pid, owner_creation_time, owner_executable, state,
|
|
acquired_at, heartbeat_at, lease_expires_at, updated_at)
|
|
VALUES (%s, 7, 'synthetic-expired', 'synthetic-expired', 999999, 'synthetic',
|
|
'/synthetic/expired', 'ready', %s, %s, %s, %s)""", (worker, STAMP, STAMP, STAMP, STAMP))
|
|
for table in ('target_scans', 'findings', 'projection_jobs', 'projection_appends', 'projection_rotations'):
|
|
connection.execute('SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence(%s, %s), '
|
|
+ '(SELECT max(id) FROM public.' + identifier(table) + '), true)', ('public.' + table, 'id'))
|
|
status_files = []
|
|
for i in range(13):
|
|
provider = f'p{i:02d}'
|
|
directory = runtime.DATA / 'runtime-linux/keychecks' / provider
|
|
directory.mkdir(mode=0o700)
|
|
path = directory / f'{provider}Checked.txt'
|
|
importer._write(runtime, path, b'synthetic status snapshot\n')
|
|
status_files.append(path)
|
|
return status_files
|
|
|
|
|
|
def fixture_manifest(connection, runtime, importer, recovery, baseline, status_files, system_identifier):
|
|
# Required source labels are inert format metadata, never opened as paths.
|
|
paths = list(status_files)
|
|
for name in sorted(importer.REQUIRED_FILES):
|
|
path = runtime.DATA / name
|
|
if not os.path.lexists(path):
|
|
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
importer._write(runtime, path, b'')
|
|
paths.append(path)
|
|
files = []
|
|
for path in paths:
|
|
with importer._input(path, runtime) as (handle, before):
|
|
raw = handle.read(4096)
|
|
require(len(raw) == before[4], 'status_fixture_bound')
|
|
files.append({'path': path.relative_to(runtime.DATA).as_posix(), 'size': len(raw), 'sha256': digest(raw)})
|
|
# Historical bytes are intentionally not materialized; this is not a restore test.
|
|
files.append({'path': 'runtime-linux/results/found_secrets.jsonl', 'size': OLD_OFFSET,
|
|
'sha256': digest(b'intentionally-unavailable-synthetic-output')})
|
|
value = {'format': 'truf-windows-snapshot-v1',
|
|
'source': {'root': r'D:\truf', 'postgres_data_dir': r'S:\postgres-data',
|
|
'supervisor_stopped': True, 'postgres_stopped': True},
|
|
'database': {'version_num': connection.execute("SELECT current_setting('server_version_num')::int AS version").fetchone()['version'],
|
|
'system_identifier': '1' if system_identifier != '1' else '2',
|
|
'database_name': 'synthetic_source', 'user_name': 'synthetic_source', 'port': 15432,
|
|
'data_directory': r'S:\postgres-data', 'bytes': 6, 'sha256': digest(b'PGDMPx'),
|
|
'table_counts': {name: item['rows'] + int(name in ('projection_streams', 'projection_cursors'))
|
|
for name, item in baseline['tables'].items()},
|
|
'sequence_states': baseline['sequences'], 'sequence_count': len(baseline['sequences']['public'])},
|
|
'archive': {'bytes': sum(item['size'] for item in files) + 10240,
|
|
'sha256': digest(b'synthetic archive identity')}, 'files': files}
|
|
raw = importer._encoded(value)
|
|
pin = digest(raw)
|
|
importer._manifest(raw, pin)
|
|
importer._write(runtime, runtime.DATA / 'config/windows-import-manifest.json', raw)
|
|
recovery.APPROVED_MANIFEST_SHA256 = pin
|
|
return pin
|
|
|
|
|
|
def exercise_projector(connection, db, runtime, config, check):
|
|
from jsonl_projector import JsonlProjector
|
|
from migrate_runtime_safety import initialize_projection_cursors_from_existing_files, require_legacy_cutover_clear
|
|
|
|
# This is a real offline cutover after recovery, not a mocked readiness gate.
|
|
db.require_runtime_safety_schema()
|
|
cursors = initialize_projection_cursors_from_existing_files(db, config)
|
|
legacy = require_legacy_cutover_clear(db, config)
|
|
migrations = db.conn.execute('SELECT version, code_sha256 FROM runtime_schema_migrations ORDER BY version').fetchall()
|
|
db.conn.commit()
|
|
db.record_final_cutover({'legacy': legacy, 'projection_cursors': cursors,
|
|
'schema_migrations': [dict(row) for row in migrations]})
|
|
db.require_final_cutover()
|
|
check()
|
|
event_id, event_hash, finding_uid = 'f' * 32, digest(b'future-event'), digest(b'future-finding')
|
|
with connection.transaction():
|
|
scan_id = connection.execute("""INSERT INTO public.target_scans(
|
|
scan_event_id, scan_event_hash, target, normalized_target, scan_type, status, ended_at,
|
|
findings_count, raw_result_storage, created_at)
|
|
VALUES (%s, %s, 'fixture:future', 'fixture:future', 'fixture', 'found', %s, 1, 'normalized_v2', %s)
|
|
RETURNING id""", (event_id, event_hash, STAMP, STAMP)).fetchone()['id']
|
|
connection.execute("""INSERT INTO public.scan_result_compat(target_scan_id, schema_version,
|
|
metadata_json, metadata_sha256, metadata_bytes, reconstruction_status, created_at)
|
|
VALUES (%s, 2, '{}', %s, 2, 'exact', %s)""", (scan_id, digest(b'{}'), STAMP))
|
|
finding_id = connection.execute("""INSERT INTO public.findings(target_scan_id, detector_name,
|
|
detector_type, verified, raw_secret, redacted_secret, finding_uid, created_at)
|
|
VALUES (%s, 'Fixture', 'fixture', 0, 'synthetic-future', 'fixture', %s, %s) RETURNING id""",
|
|
(scan_id, finding_uid, STAMP)).fetchone()['id']
|
|
connection.execute("""INSERT INTO public.finding_compat_payloads(finding_id, raw_value,
|
|
extension_json, payload_sha256, payload_bytes, payload_omitted, created_at)
|
|
VALUES (%s, 'synthetic-future', '{}', %s, 16, 0, %s)""", (finding_id, digest(b'synthetic-future'), STAMP))
|
|
job_id = connection.execute("""INSERT INTO public.projection_jobs(job_kind, event_id, event_hash,
|
|
target_scan_id, status, required_stream_mask, capacity_items, capacity_bytes, created_at, updated_at)
|
|
VALUES ('scan_event', %s, %s, %s, 'pending', 2, 1, 65536, %s, %s) RETURNING id""",
|
|
(event_id, event_hash, scan_id, STAMP, STAMP)).fetchone()['id']
|
|
connection.execute("""UPDATE public.pipeline_capacity SET projection_items = projection_items + 1,
|
|
projection_bytes = projection_bytes + 65536 WHERE id = 1""")
|
|
worker = JsonlProjector(db, str(runtime.DATA / 'runtime-linux/results'), 'projection-loss-e2e',
|
|
keycheck_dir=str(runtime.DATA / 'runtime-linux/keychecks'))
|
|
try:
|
|
worker.start()
|
|
require(worker.process_one() is True, 'projector_processed')
|
|
require(worker.process_one() is False, 'projector_idle')
|
|
row = connection.execute("""SELECT a.generation, a.byte_offset, a.byte_length,
|
|
a.payload_sha256, a.state, j.status, j.capacity_released
|
|
FROM public.projection_appends a JOIN public.projection_jobs j ON j.id = a.job_id
|
|
WHERE a.job_id = %s AND a.stream_name = 'found_secrets'""", (job_id,)).fetchone()
|
|
path = runtime.DATA / 'runtime-linux/results/found_secrets.jsonl'
|
|
runtime.private_path(path)
|
|
require(path.stat().st_size < 65536, 'bounded_new_output')
|
|
raw = path.read_bytes()
|
|
require(row and row['generation'] == 14 and row['byte_offset'] == 0
|
|
and row['byte_length'] == len(raw) and row['payload_sha256'] == digest(raw)
|
|
and row['state'] == 'appended' and row['status'] == 'completed' and row['capacity_released'] == 1,
|
|
'projector_fenced_append')
|
|
require(json.loads(raw)['finding_uid'] == finding_uid, 'projector_real_payload')
|
|
cursor = metadata(connection)['cursor']
|
|
require(cursor['generation'] == 14 and cursor['committed_offset'] == len(raw)
|
|
and cursor['last_job_id'] == job_id, 'projector_cursor')
|
|
capacity = connection.execute('SELECT projection_items, projection_bytes FROM public.pipeline_capacity WHERE id = 1').fetchone()
|
|
require(capacity == {'projection_items': 0, 'projection_bytes': 0}, 'projector_capacity')
|
|
emit(stage='projector', passed=True, generation=14, records=1, bytes=len(raw))
|
|
finally:
|
|
worker.stop()
|
|
|
|
|
|
def run_cases(connection, connect, resources, runtime, config, identity, initialize_lock, authority_lock, check):
|
|
import container_import as importer
|
|
import container_projection_recovery as recovery
|
|
from runtime_security import PrivateFileLock
|
|
from scanner_db import ScannerDB, migrate_runtime_safety_schema
|
|
|
|
schema_db = ScannerDB(db_url=os.environ['SCANNER_DB_URL'], initialize=False)
|
|
resources.append(schema_db)
|
|
require(schema_db.enabled and schema_db.conn.is_postgres, 'real_schema_connection')
|
|
try:
|
|
migrate_runtime_safety_schema(schema_db, initialize_base=True)
|
|
schema_db.require_runtime_safety_schema()
|
|
finally:
|
|
schema_db.close()
|
|
emit(stage='schema', passed=True)
|
|
check()
|
|
status_files = seed_history(connection, runtime, importer)
|
|
emit(stage='seed', passed=True)
|
|
baseline = proof(connection, check)
|
|
emit(stage='proof', passed=True, tables=len(baseline['tables']), sequences=len(baseline['sequences']['public']))
|
|
before = metadata(connection)
|
|
require(before['stream']['current_generation'] == before['cursor']['generation'] == 13
|
|
and before['cursor']['committed_offset'] == OLD_OFFSET, 'reviewed_before')
|
|
require(baseline['tables']['projection_appends']['rows'] == APPENDS
|
|
and baseline['tables']['projection_rotations']['rows'] == 13
|
|
and baseline['tables']['projection_append_audit']['rows'] == 0, 'reviewed_history')
|
|
original_pin = recovery.APPROVED_MANIFEST_SHA256
|
|
pin = fixture_manifest(connection, runtime, importer, recovery, baseline, status_files, identity['system_identifier'])
|
|
journal_path = runtime.DATA / 'config' / recovery.JOURNAL_NAME
|
|
status_before = {path: (path.stat().st_ino, path.read_bytes()) for path in status_files}
|
|
emit(stage='fixture', passed=True, streams=34, appends=APPENDS, rotations=13,
|
|
tables=len(baseline['tables']), sequences=len(baseline['sequences']['public']))
|
|
|
|
def recover(observed):
|
|
return recovery.recover_found_secrets_projection(runtime, observed,
|
|
system_identifier=identity['system_identifier'], manifest_sha256=pin,
|
|
initialize_lock=initialize_lock, authority_lock=authority_lock)
|
|
|
|
def refused(observed):
|
|
try:
|
|
recover(observed)
|
|
except recovery.ProjectionRecoveryError as error:
|
|
if not observed.injected and (observed.after_update or observed.lost_ack):
|
|
emit(stage='fault_not_reached', passed=False, **safe_error(error))
|
|
return
|
|
raise AssertionError('expected_recovery_refusal')
|
|
|
|
def alone():
|
|
while True:
|
|
check()
|
|
connection.execute('SELECT pg_catalog.pg_stat_clear_snapshot()')
|
|
count = connection.execute("""SELECT count(*) AS count FROM pg_catalog.pg_stat_activity
|
|
WHERE backend_type = 'client backend' AND pid <> pg_backend_pid()""").fetchone()['count']
|
|
if count == 0:
|
|
return
|
|
time.sleep(0.05)
|
|
|
|
try:
|
|
alone()
|
|
contender = connect()
|
|
resources.append(contender)
|
|
try:
|
|
observed = ObservedConnection(connection, runtime, recovery, check)
|
|
refused(observed)
|
|
require(observed.updates == 0, 'other_client_no_updates')
|
|
with contender.transaction():
|
|
contender.execute('LOCK TABLE public.projection_streams IN ROW EXCLUSIVE MODE')
|
|
observed = ObservedConnection(connection, runtime, recovery, check)
|
|
refused(observed)
|
|
require(observed.updates == 0, 'writer_contention_no_updates')
|
|
finally:
|
|
contender.close()
|
|
alone()
|
|
with PrivateFileLock(str(runtime.DATA / 'runtime-linux/results/.jsonl-projector.lock')):
|
|
observed = ObservedConnection(connection, runtime, recovery, check)
|
|
refused(observed)
|
|
require(observed.updates == 0, 'projector_file_lock_no_updates')
|
|
require(metadata(connection) == before and proof(connection, check) == baseline
|
|
and not os.path.lexists(journal_path), 'contention_unchanged')
|
|
emit(stage='contention', passed=True, cases=3, updates=0)
|
|
|
|
observed = ObservedConnection(connection, runtime, recovery, check, after_update=True)
|
|
refused(observed)
|
|
require(observed.injected and observed.updates == 1 and observed.commits == 0, 'rollback_fault_window')
|
|
require(int(connection.info.transaction_status) == 0 and metadata(connection) == before
|
|
and proof(connection, check) == baseline, 'both_rows_rolled_back')
|
|
journal = journal_snapshot(runtime, recovery)
|
|
require(journal[2]['record']['before'] == before, 'journal_before')
|
|
emit(stage='rollback', passed=True, updates=1, commits=0, journal_retained=True)
|
|
|
|
observed = ObservedConnection(connection, runtime, recovery, check, lost_ack=True)
|
|
refused(observed)
|
|
require(observed.injected and observed.updates == 2 and observed.commits == 1, 'real_commit_before_ack_loss')
|
|
after = metadata(connection)
|
|
require(after == journal[2]['record']['after'] and proof(connection, check) == baseline
|
|
and journal_snapshot(runtime, recovery) == journal, 'committed_despite_ack_loss')
|
|
emit(stage='lost_ack', passed=True, updates=2, commits=1, history_unchanged=True)
|
|
|
|
observed = ObservedConnection(connection, runtime, recovery, check)
|
|
result = recover(observed)
|
|
require(result['status'] == 'already-committed' and observed.updates == 0
|
|
and result['journal_sha256'] == digest(journal[0]) and metadata(connection) == after
|
|
and proof(connection, check) == baseline and journal_snapshot(runtime, recovery) == journal,
|
|
'idempotent_readonly_retry')
|
|
require({path: (path.stat().st_ino, path.read_bytes()) for path in status_files} == status_before,
|
|
'other_output_unchanged')
|
|
emit(stage='retry', passed=True, updates=0, journal_unchanged=True, history_unchanged=True, sequences_unchanged=True)
|
|
|
|
writer_db = ScannerDB(db_url=os.environ['SCANNER_DB_URL'], initialize=False)
|
|
resources.append(writer_db)
|
|
require(writer_db.enabled and writer_db.conn.is_postgres, 'real_writer_connection')
|
|
try:
|
|
exercise_projector(connection, writer_db, runtime, config, check)
|
|
finally:
|
|
writer_db.close()
|
|
alone()
|
|
advanced, advanced_proof = metadata(connection), proof(connection, check)
|
|
future_path = runtime.DATA / 'runtime-linux/results/found_secrets.jsonl'
|
|
future_bytes, future_inode = future_path.read_bytes(), future_path.stat().st_ino
|
|
observed = ObservedConnection(connection, runtime, recovery, check)
|
|
refused(observed)
|
|
require(observed.updates == 0 and advanced['cursor']['committed_offset'] > 0
|
|
and metadata(connection) == advanced and proof(connection, check) == advanced_proof
|
|
and (future_path.read_bytes(), future_path.stat().st_ino) == (future_bytes, future_inode)
|
|
and journal_snapshot(runtime, recovery) == journal, 'future_output_not_rewound')
|
|
emit(stage='future_output', passed=True, updates=0, advanced_cursor_retained=True, journal_unchanged=True)
|
|
finally:
|
|
recovery.APPROVED_MANIFEST_SHA256 = original_pin
|
|
|
|
|
|
def main():
|
|
global OUTPUT
|
|
# Native pg_ctl also inherits fd 1/2: Python stream redirection alone is insufficient.
|
|
OUTPUT = os.fdopen(os.dup(1), 'w', encoding='utf-8', buffering=1)
|
|
sink = os.open(os.devnull, os.O_WRONLY)
|
|
try:
|
|
os.dup2(sink, 1)
|
|
os.dup2(sink, 2)
|
|
finally:
|
|
os.close(sink)
|
|
parser = argparse.ArgumentParser(allow_abbrev=False)
|
|
parser.add_argument('--budget-seconds', type=int, default=300)
|
|
args = parser.parse_args()
|
|
require(30 <= args.budget_seconds <= 3600, 'budget')
|
|
started = time.monotonic()
|
|
deadline = started + args.budget_seconds
|
|
require(sys.platform == 'linux' and os.geteuid() == os.getuid() == 10001
|
|
and os.getegid() == os.getgid() == 10001, 'test_identity')
|
|
require(Path(__file__) == IMAGE_PATH and sys.flags.isolated and sys.flags.no_site
|
|
and sys.flags.dont_write_bytecode, 'test_image')
|
|
require({name for _, name in socket.if_nameindex()} == {'lo'}, 'offline_test')
|
|
raw_mounts = Path('/proc/self/mountinfo').read_bytes()
|
|
require(len(raw_mounts) <= 1024 * 1024, 'mount_bound')
|
|
mounts = [line.split() for line in raw_mounts.decode('utf-8', errors='strict').splitlines()]
|
|
data = [row for row in mounts if len(row) > 6 and (row[4] == '/data' or row[4].startswith('/data/'))]
|
|
require(len(data) == 1 and data[0][4] == '/data' and '-' in data[0]
|
|
and re.fullmatch(r'/var/lib/docker/volumes/truf-projection-loss-test-[a-f0-9]{32}/_data', data[0][3])
|
|
and data[0][data[0].index('-') + 1] == 'ext4', 'fresh_test_volume')
|
|
runtime = SimpleNamespace(**runpy.run_path('/opt/truf/app/container_runtime.py'))
|
|
runtime.require_container()
|
|
runtime.private_path(IMAGE_PATH.parent, directory=True)
|
|
runtime.private_path(IMAGE_PATH)
|
|
runtime._shutdown_requested = False
|
|
for sig in (signal.SIGTERM, signal.SIGINT, signal.SIGHUP):
|
|
signal.signal(sig, lambda *_: setattr(runtime, '_shutdown_requested', True))
|
|
|
|
def fresh():
|
|
runtime.private_path(runtime.DATA / 'postgres-linux', directory=True)
|
|
require(not any((runtime.DATA / 'postgres-linux').iterdir()), 'empty_pgdata')
|
|
require(not any((runtime.DATA / 'runtime-linux/results').iterdir()), 'empty_results')
|
|
for name in ('runtime-linux/postgres/cluster_identity.json', 'initialized.json',
|
|
'config/windows-import-manifest.json', 'config/found-secrets-loss-g13-g14.prepared.json'):
|
|
require(not os.path.lexists(runtime.DATA / name), 'fresh_fixture')
|
|
|
|
fresh()
|
|
config_path = runtime.DEFAULT_CONFIG
|
|
config = runtime.prepare_environment(config_path)
|
|
os.environ.update(TRUF_DB_STATEMENT_TIMEOUT_MS='120000', TRUF_DB_LOCK_TIMEOUT_MS='5000',
|
|
TRUF_DB_IDLE_TRANSACTION_TIMEOUT_MS='300000')
|
|
import postgres_runtime as pg
|
|
import psycopg
|
|
from psycopg.rows import dict_row
|
|
from runtime_security import ClusterAuthorityLock, PrivateFileLock
|
|
|
|
def connect():
|
|
return psycopg.connect(os.environ['SCANNER_DB_URL'], autocommit=True, row_factory=dict_row,
|
|
connect_timeout=5, application_name='truf-projection-loss-e2e', tcp_user_timeout=30000,
|
|
options='-c search_path=public -c statement_timeout=120000 -c lock_timeout=5000 '
|
|
'-c idle_in_transaction_session_timeout=300000 -c row_security=off '
|
|
'-c log_min_error_statement=panic -c log_min_messages=panic '
|
|
'-c log_statement=none -c log_min_duration_statement=-1')
|
|
|
|
resources = []
|
|
check = lambda: checkpoint(runtime, deadline)
|
|
with PrivateFileLock(str(runtime.INITIALIZE_LOCK)) as initialize_lock:
|
|
fresh()
|
|
authority_lock = ClusterAuthorityLock(config, endpoint_dsn=os.environ['SCANNER_DB_URL'])
|
|
code = initialize_empty(runtime, config_path)
|
|
while True:
|
|
try:
|
|
authority_lock.acquire()
|
|
break
|
|
except BaseException as error:
|
|
emit(stage='authority', failed_hold=True, **safe_error(error))
|
|
hold_pause()
|
|
try:
|
|
backend = None
|
|
while backend is None:
|
|
try:
|
|
backend = pg.PostgresBackend(config, stop_timeout_sec=60)
|
|
except BaseException as error:
|
|
emit(stage='backend', failed_hold=True, **safe_error(error))
|
|
hold_pause()
|
|
try:
|
|
require(code == 0, 'initialize_empty_exit')
|
|
check()
|
|
require(backend.probe().kind == pg.ProbeKind.STOPPED, 'initial_stopped_probe')
|
|
identity = pg.verify_cluster_identity(config)
|
|
require(identity['pg_major'] == 16 and identity['data_directory'] == '/data/postgres-linux', 'bound_fixture')
|
|
require(backend.start().accepted is True, 'direct_backend_start')
|
|
while True:
|
|
check()
|
|
probe = backend.probe()
|
|
if probe.kind == pg.ProbeKind.READY:
|
|
break
|
|
require(probe.kind == pg.ProbeKind.RECOVERING, 'authenticated_start')
|
|
time.sleep(0.1)
|
|
emit(stage='start', ready=True)
|
|
connection = connect()
|
|
resources.append(connection)
|
|
count = connection.execute("""SELECT count(*) AS count FROM pg_catalog.pg_class c
|
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE n.nspname = 'public' AND c.relkind IN ('r','p','f')""").fetchone()['count']
|
|
require(count == 0, 'virgin_schema')
|
|
run_cases(connection, connect, resources, runtime, config, identity, initialize_lock, authority_lock, check)
|
|
require(not os.path.lexists(runtime.INITIALIZED), 'no_application_marker')
|
|
finally:
|
|
try:
|
|
for resource in reversed(resources):
|
|
try:
|
|
resource.close()
|
|
except BaseException as error:
|
|
emit(stage='client_close', failed_hold=True, **safe_error(error))
|
|
finally:
|
|
stop_confirmed(backend, pg)
|
|
finally:
|
|
authority_lock.release()
|
|
emit(stage='finished', passed=True, stopped=STOPPED, application_initialized=False,
|
|
elapsed_ms=round((time.monotonic() - started) * 1000))
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
result = main()
|
|
except BaseException as error:
|
|
emit(stage='finished', passed=False, stopped=STOPPED, **safe_error(error))
|
|
result = 1
|
|
raise SystemExit(result)
|