mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
test(s3_v2): drop the libfaketime hour rollover cell and its dev dependency
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
ee8d21604a
commit
79a71c8550
4 changed files with 0 additions and 129 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
15
uv.lock
generated
15
uv.lock
generated
|
|
@ -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" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue