diff --git a/pyproject.toml b/pyproject.toml index bb01ff9f040..fb21d8fa23b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -236,7 +236,6 @@ dev = [ "pytest-timeout==2.4.0", "vcrpy==8.2.1", "pytest-recording==0.13.4", - "libfaketime==3.0.1", ] e2e-dev = [ "playwright==1.61.0", diff --git a/tests/code_coverage_tests/liccheck.ini b/tests/code_coverage_tests/liccheck.ini index ccaea33e6f4..8a3e880043b 100644 --- a/tests/code_coverage_tests/liccheck.ini +++ b/tests/code_coverage_tests/liccheck.ini @@ -169,7 +169,6 @@ pygithub: >=2.8.1 # LGPL license argon2-cffi: >=25.1.0 # MIT License blockbuster: >=1.5.26 # Apache 2.0 license pylint: >=3.3.9 # GPLv2 license -libfaketime: >=3.0.1 # GPLv2 license, dev-only LD_PRELOAD clock for tests/integration, https://github.com/simon-weber/python-libfaketime httpx2: >=2.5.0 # BSD 3-Clause License httpcore2: >=2.5.0 # BSD 3-Clause License mcp-types: >=2.2.0 # MIT License diff --git a/tests/integration/observability/test_s3_v2_partition_granularity.py b/tests/integration/observability/test_s3_v2_partition_granularity.py index 7033e530a91..3e6448f746d 100644 --- a/tests/integration/observability/test_s3_v2_partition_granularity.py +++ b/tests/integration/observability/test_s3_v2_partition_granularity.py @@ -7,7 +7,6 @@ from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager from dataclasses import dataclass, field from datetime import datetime, timedelta -from importlib.resources import files from pathlib import Path from typing import Final from urllib.parse import quote, unquote @@ -37,7 +36,6 @@ from integration._support.wire import Reply, Request, wire_server FLUSH: Final = {"DEFAULT_S3_FLUSH_INTERVAL_SECONDS": "1"} HOUR: Final = {"s3_partition_granularity": "hour"} -FAKETIME_LIBRARY: Final = files("libfaketime").joinpath("vendor", "libfaketime", "src", "libfaketime.so.1") ANTHROPIC_MODEL: Final = "anthropic/claude-sonnet-4-5-20250929" WARNING: Final = "s3 logging: s3_partition_granularity=" SINK_CREDENTIALS: Final = { @@ -207,23 +205,6 @@ def _keys_on_fresh_connections(candidate: Gateway, aliases: tuple[str, ...]) -> return tuple(pool.map(generate, aliases)) -SETUP_AUDITS: Final = ( - ("created", "LiteLLM_ProxyModelTable"), - ("created", "LiteLLM_ProxyModelTable"), - ("created", "LiteLLM_VerificationToken"), -) - - -def _audit_changes(sink: RecordingS3Sink, audit_prefix: str) -> tuple[tuple[str, str], ...]: - bodies: Final = (body for target, body in sink.objects().items() if target.startswith(audit_prefix)) - lines: Final = b"\n".join(bodies).splitlines() - return tuple(sorted((str(audit["action"]), str(audit["table_name"])) for audit in map(_audit_line, lines))) - - -def _audit_line(line: bytes) -> Mapping[str, JsonValue]: - return object_value(json.loads(line)) - - def _created_key_hashes(sink: RecordingS3Sink, audit_prefix: str) -> frozenset[str]: created: Final = ( object_value(json.loads(body)) for target, body in sink.objects().items() if target.startswith(audit_prefix) @@ -1083,99 +1064,6 @@ def test_s3_v2_hour_coded_403_retries_reuse_the_same_hour_key(gateway: Gateway, assert _outside_layout(objects, "hour") == () -def test_s3_v2_hour_rollover_inside_one_batch_flush_splits_files_by_hour_folder( - gateway: Gateway, tmp_path: Path -) -> None: - assert FAKETIME_LIBRARY.is_file(), "the libfaketime dev dependency drives the proxy clock" - marker: Final = "s3hroll" + uuid.uuid4().hex[:8] - clock: Final = tmp_path / "proxy-clock" - clock.write_text("@2026-09-29 10:59:30\n") - upstream: Final = CountingUpstream() - sink: Final = RecordingS3Sink(delay_seconds=0.05) - audit_prefix: Final = f"/{BUCKET}/{PREFIX}/audit_logs/" - with ( - wire_server(upstream.respond) as provider, - wire_server(sink.respond) as bucket, - _s3_proxy( - gateway, - tmp_path, - bucket.url, - {**HOUR, "s3_batch_file_upload": True}, - {"store_audit_logs": True, "audit_log_callbacks": ["s3_v2"]}, - environment={ - "DEFAULT_S3_FLUSH_INTERVAL_SECONDS": "3600", - "DEFAULT_S3_BATCH_SIZE": "1", - "LD_PRELOAD": str(FAKETIME_LIBRARY), - "FAKETIME_TIMESTAMP_FILE": str(clock), - "FAKETIME_CACHE_DURATION": "1", - "FAKETIME_DONT_FAKE_MONOTONIC": "1", - }, - workers=1, - ) as owned, - owned.gateway.scenario() as scenario, - ): - openai_model, _, key = _models(scenario, provider.url) - setup_audits: Final = eventually( - lambda: _audit_changes(sink, audit_prefix), - lambda changes: changes == SETUP_AUDITS, - seconds=30, - ) - - def advance(stamp: str, shown: str) -> None: - clock.write_text(f"@2026-09-29 {stamp}\n") - eventually( - lambda: owned.gateway.client.get("/health/liveliness").headers["date"], - lambda date: f" {shown}:" in date, - seconds=10, - ) - - advance("10:59:40", "10:59") - flushed_before: Final = frozenset(sink.objects()) - before: Final = tuple(f"{marker}-before-{index}" for index in range(4)) - after: Final = tuple(f"{marker}-after-{index}" for index in range(4)) - answered_before: Final = _sdk_chats(owned.gateway, openai_model, key, before) - advance("11:00:05", "11:00") - answered_after: Final = _sdk_chats(owned.gateway, openai_model, key, after) - spent: Final = eventually( - lambda: read_rows( - 'SELECT request_id FROM "LiteLLM_SpendLogs" WHERE request_id = ANY(%s)', - (list(answered_before + answered_after),), - ), - lambda rows: len(rows) == len(before) + len(after), - seconds=70, - ) - pending: Final = frozenset(sink.objects()) - flushed_before - scenario.key(models=[openai_model]) - batches: Final = eventually( - lambda: { - target: body - for target, body in sink.objects().items() - if target not in flushed_before and not target.startswith(audit_prefix) - }, - lambda landed: sum(len(body.splitlines()) for body in landed.values()) >= len(before) + len(after), - seconds=30, - ) - batch_file: Final = re.compile( - rf"/{BUCKET}/{PREFIX}/2026-09-29/(\d{{2}})/batch_(\d{{2}}-\d{{2}}-\d{{2}})_[0-9a-f]{{32}}\.jsonl" - ) - layout: Final = { - (match.group(1), match.group(2)): sorted(_prompt(object_value(json.loads(line))) for line in body.splitlines()) - for target, body in batches.items() - if (match := batch_file.fullmatch(unquote(target))) - } - assert setup_audits == SETUP_AUDITS - assert answered_before == before and answered_after == after - assert sorted(str(row["request_id"]) for row in spent) == sorted(before + after) - assert sorted(upstream.received()) == sorted(before + after), "every prompt must reach the upstream exactly once" - assert pending == frozenset(), ( - f"request logs must stay queued until the audit log fills the batch: {sorted(pending)}" - ) - assert len(layout) == len(batches) == 2, tuple(batches) - assert sorted(hour for hour, _ in layout) == ["10", "11"], layout - assert len({stamp for _, stamp in layout}) == 1, f"both hour files must come from one flush: {layout}" - assert {hour: prompts for (hour, _), prompts in layout.items()} == {"10": sorted(before), "11": sorted(after)} - - def test_s3_v2_hour_slow_sink_batches_never_duplicate_an_upload(gateway: Gateway, tmp_path: Path) -> None: marker: Final = "s3hslow" + uuid.uuid4().hex[:8] upstream: Final = CountingUpstream() diff --git a/uv.lock b/uv.lock index d39c331e85e..527f53bd372 100644 --- a/uv.lock +++ b/uv.lock @@ -4399,19 +4399,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/a0/b91504515c1f9a299fc157967ffbd2f0321bce0516a3d5b89f6f4cad0355/lazy_object_proxy-1.12.0-pp39.pp310.pp311.graalpy311-none-any.whl", hash = "sha256:c3b2e0af1f7f77c4263759c4824316ce458fabe0fceadcd24ef8ca08b2d1e402", size = 15072, upload-time = "2025-08-22T13:50:05.498Z" }, ] -[[package]] -name = "libfaketime" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, - { name = "pytz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/85/b46c502837430823420be8ba856479819acc5a5fa4e46de167b0188f4ba3/libfaketime-3.0.1.tar.gz", hash = "sha256:49c2b06250bd5a1206efe5ecfdc575850517081b50b42c6c58718be1a4a09fbd", size = 75878, upload-time = "2026-08-19T16:44:13.598Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/a3/af0d453324bff5ee0c1ae51be5604df56c5c6d1d5f61e430a1b96a80e2ac/libfaketime-3.0.1-py3-none-any.whl", hash = "sha256:5eed7c17f3d4ba0f6f828de4975f69a622b06b1e792674e6c1b069c0689fab79", size = 92987, upload-time = "2026-08-19T16:44:12.085Z" }, -] - [[package]] name = "librt" version = "0.15.0" @@ -4700,7 +4687,6 @@ dev = [ { name = "hypothesis" }, { name = "keyring" }, { name = "langfuse" }, - { name = "libfaketime" }, { name = "mypy" }, { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, @@ -4916,7 +4902,6 @@ dev = [ { name = "hypothesis", specifier = "==6.165.10" }, { name = "keyring", specifier = "==25.7.0" }, { name = "langfuse", specifier = ">=4.7,<5.0" }, - { name = "libfaketime", specifier = "==3.0.1" }, { name = "mypy", specifier = "==1.20.1" }, { name = "numpy", specifier = ">=1.26.0,<3.0" }, { name = "openapi-core", specifier = "==0.22.0" },