[Frontend] Configurable video storage backends with TTL (#2531)

Signed-off-by: Ian Eaves <ian.k.eaves@gmail.com>
This commit is contained in:
Ian Eaves authored and GitHub committed 2026-06-19 17:41:22 -06:00
1 parent 7e152ca64b
commit 951ae28eb0
12 files changed
+531 -66

No files matched your search

@@ -0,0 +1,94 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""
Unit tests for the OpenAI video storage managers.
"""
import asyncio
import os
import time
import pytest
from vllm_omni.entrypoints.openai import storage as storage_module
from vllm_omni.entrypoints.openai.storage import FileStorageHandle, LocalStorageManager, LocalStorageTTLManager
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_local_storage_open_returns_file_handle_for_saved_key(tmp_path):
storage = LocalStorageManager(storage_path=str(tmp_path / "storage"))
save_context = asyncio.run(storage.save(b"video-bytes", "video-123"))
handle = asyncio.run(storage.open(save_context.key))
assert isinstance(handle, FileStorageHandle)
assert handle.path == os.path.join(storage.storage_path, "video-123")
with open(handle.path, "rb") as file:
assert file.read() == b"video-bytes"
def test_local_storage_open_returns_none_for_missing_key(tmp_path):
storage = LocalStorageManager(storage_path=str(tmp_path / "storage"))
handle = asyncio.run(storage.open("missing-video"))
assert handle is None
def test_local_storage_ttl_save_sets_expiration_metadata(tmp_path, monkeypatch):
storage = LocalStorageTTLManager(
storage_path=str(tmp_path / "storage"),
max_concurrency=1,
ttl_seconds=60,
sweep_interval_seconds=300,
)
monkeypatch.setattr(storage_module.time, "time", lambda: 1_700_000_000)
save_context = asyncio.run(storage.save(b"video-bytes", "video-ttl"))
assert save_context.key == "video-ttl"
assert save_context.created_at == 1_700_000_000
assert save_context.expires_at == 1_700_000_060
def test_local_storage_ttl_sweeper_removes_expired_file(tmp_path):
storage = LocalStorageTTLManager(
storage_path=str(tmp_path / "storage"),
max_concurrency=1,
ttl_seconds=1,
sweep_interval_seconds=60,
)
async def setup_file() -> tuple[str, str]:
save_context = await storage.save(b"video-bytes", "video-expired")
file_path = storage.get_full_file_path(save_context.key)
return save_context.key, file_path
storage_key, file_path = asyncio.run(setup_file())
expired_mtime = time.time() - 10
os.utime(file_path, (expired_mtime, expired_mtime))
assert os.path.exists(file_path)
deleted = asyncio.run(storage._sweep_once(time.time() - 1))
assert deleted == 1
assert not os.path.exists(file_path)
assert asyncio.run(storage.open(storage_key)) is None
def test_local_storage_ttl_sweeper_keeps_files_when_path_missing(tmp_path):
storage = LocalStorageTTLManager(
storage_path=str(tmp_path / "storage"),
max_concurrency=1,
ttl_seconds=1,
sweep_interval_seconds=60,
)
missing_path = storage.get_full_file_path("video-missing")
assert not os.path.exists(missing_path)
deleted = asyncio.run(storage._sweep_once(time.time() - 1))
assert deleted == 0
assert not os.path.exists(missing_path)
@@ -1020,6 +1020,31 @@ def test_invalid_lora_returns_400(test_client):
assert "lora object" in failed["error"]["message"].lower()
def test_failed_generation_awaits_storage_cleanup(test_client, isolated_video_backends, mocker: MockerFixture):
"""Regression (merge seam): when async generation raises, the failure handler
must ``await _cleanup_video(video_id)`` (single-arg, async) and still record
FAILED. Upstream carried a sync ``_cleanup_video(video_id, output_path)`` whose
stale call in the generic handler sat outside the conflict markers; against the
PR's async storage manager that raised NameError before the FAILED update,
wedging the job in IN_PROGRESS and orphaning the artifact."""
_store, _tasks, storage = isolated_video_backends
delete_spy = mocker.spy(storage, "delete")
mocker.patch.object(
OmniOpenAIServingVideo,
"generate_video_bytes",
side_effect=RuntimeError("GPU exploded"),
)
response = test_client.post("/v1/videos", data={"prompt": "will fail"})
assert response.status_code == 200
video_id = response.json()["id"]
failed = _wait_for_status(test_client, video_id, VideoGenerationStatus.FAILED.value)
assert failed["error"]["code"] == 500
assert "GPU exploded" in failed["error"]["message"]
delete_spy.assert_called_once_with(video_id)
def test_async_guardrail_error_returns_400_on_retrieve(test_client, mocker: MockerFixture):
mocker.patch.object(
OmniOpenAIServingVideo,
@@ -1178,7 +1203,7 @@ def test_delete_completed_job_removes_file_and_metadata(test_client, mocker: Moc
final = _wait_for_status(test_client, video_id, VideoGenerationStatus.COMPLETED.value)
file_name = final["file_name"]
assert file_name is not None
file_path = os.path.join(api_server.STORAGE_MANAGER.storage_path, file_name)
file_path = os.path.join(api_server.STORAGE_MANAGER.storage_path, video_id)
assert os.path.exists(file_path)
delete_resp = test_client.delete(f"/v1/videos/{video_id}")
@@ -1189,6 +1214,30 @@ def test_delete_completed_job_removes_file_and_metadata(test_client, mocker: Moc
assert not os.path.exists(file_path)
def test_download_completed_job_uses_storage_open_and_download_name(test_client, mocker: MockerFixture):
video_bytes = b"stored-video-data"
mocker.patch(
"vllm_omni.entrypoints.openai.serving_video._encode_video_bytes",
return_value=video_bytes,
)
create_resp = test_client.post("/v1/videos", data={"prompt": "Download this video"})
assert create_resp.status_code == 200
video_id = create_resp.json()["id"]
final = _wait_for_status(test_client, video_id, VideoGenerationStatus.COMPLETED.value)
file_name = final["file_name"]
assert file_name == f"{video_id}.mp4"
storage_path = os.path.join(api_server.STORAGE_MANAGER.storage_path, video_id)
assert os.path.exists(storage_path)
response = test_client.get(f"/v1/videos/{video_id}/content")
assert response.status_code == 200
assert response.content == video_bytes
assert response.headers["content-type"] == "video/mp4"
assert file_name in response.headers["content-disposition"]
def test_delete_in_progress_job_cancels_task_and_removes_metadata(test_client):
handler = BlockingVideoHandler()
test_client.app.state.openai_serving_video = handler