# SPDX-License-Identifier: Apache-2.0 """ Integration tests for the Aerospike L2 adapter (native connector). Requires Aerospike CE and BUILD_AEROSPIKE=1 extension. Skipped otherwise. """ # Standard import os import select # Third Party import pytest import torch # First Party from lmcache.v1.distributed.api import MemoryLayoutDesc, ObjectKey from lmcache.v1.distributed.l2_adapters.factory import create_l2_adapter_from_registry from lmcache.v1.memory_management import ( MemoryFormat, MemoryObjMetadata, TensorMemoryObj, ) from lmcache.v1.platform import consume_fd _EMPTY_LAYOUT = MemoryLayoutDesc(shapes=[], dtypes=[]) AEROSPIKE_HOST = os.environ.get("AEROSPIKE_TEST_HOST", "127.0.0.1") AEROSPIKE_PORT = int(os.environ.get("AEROSPIKE_TEST_PORT", "3000")) AEROSPIKE_NAMESPACE = os.environ.get("AEROSPIKE_TEST_NAMESPACE", "lmcache") RUN_AEROSPIKE_IT = os.environ.get("RUN_AEROSPIKE_INTEGRATION") == "1" def _aerospike_available() -> bool: if not RUN_AEROSPIKE_IT: return False try: # Third Party import aerospike client = aerospike.client( {"hosts": [(AEROSPIKE_HOST, AEROSPIKE_PORT)]} ).connect() info = client.info_random_node(f"namespace/{AEROSPIKE_NAMESPACE}") client.close() return "nsup-period" in info except Exception: return False def _native_extension_available() -> bool: try: # First Party from lmcache.lmcache_aerospike import LMCacheAerospikeClient # noqa: F401 return True except ImportError: return False requires_aerospike = pytest.mark.skipif( not _aerospike_available(), reason=( f"Aerospike not available at {AEROSPIKE_HOST}:{AEROSPIKE_PORT} " "(set RUN_AEROSPIKE_INTEGRATION=1)" ), ) requires_native = pytest.mark.skipif( not _native_extension_available(), reason="lmcache.lmcache_aerospike extension not built", ) def _wait_fd(fd: int, timeout: float = 30.0) -> None: poller = select.poll() poller.register(fd, select.POLLIN) events = poller.poll(timeout * 1000) assert events, "timed out waiting for eventfd" try: consume_fd(fd) except BlockingIOError: pass def _make_tensor_obj(size: int, fill: float) -> TensorMemoryObj: raw_data = torch.empty(size, dtype=torch.float32) raw_data.fill_(fill) metadata = MemoryObjMetadata( shape=torch.Size([size]), dtype=torch.float32, address=0, phy_size=size * 4, fmt=MemoryFormat.KV_2LTD, ref_count=1, ) return TensorMemoryObj(raw_data, metadata, parent_allocator=None) def _object_key(suffix: int) -> ObjectKey: return ObjectKey( chunk_hash=ObjectKey.IntHash2Bytes(suffix), model_name="aerospike-it", kv_rank=0, ) def _adapter_config(): # First Party from lmcache.v1.distributed.l2_adapters.aerospike_l2_adapter import ( AerospikeL2AdapterConfig, ) return AerospikeL2AdapterConfig( hosts=f"{AEROSPIKE_HOST}:{AEROSPIKE_PORT}", namespace=AEROSPIKE_NAMESPACE, set_name="kv_chunks_aerospike_it", num_workers=2, ) @requires_aerospike @requires_native class TestAerospikeL2Integration: def test_store_lookup_load_roundtrip(self): adapter = create_l2_adapter_from_registry(_adapter_config()) try: key = _object_key(9001) store_obj = _make_tensor_obj(64, 42.0) load_obj = _make_tensor_obj(64, 0.0) tid = adapter.submit_store_task([key], [store_obj]) _wait_fd(adapter.get_store_event_fd()) done = adapter.pop_completed_store_tasks() assert done[tid].is_successful() lookup_tid = adapter.submit_lookup_and_lock_task([key], _EMPTY_LAYOUT) _wait_fd(adapter.get_lookup_and_lock_event_fd()) lookup_bm = adapter.query_lookup_and_lock_result(lookup_tid) assert lookup_bm is not None assert lookup_bm.test(0) load_tid = adapter.submit_load_task([key], [load_obj]) _wait_fd(adapter.get_load_event_fd()) load_bm = adapter.query_load_result(load_tid) assert load_bm is not None assert load_bm.test(0) assert torch.all(load_obj.tensor == 42.0) adapter.submit_unlock([key]) finally: adapter.close()