Skip to content

Commit c3ca182

Browse files
authored
Refactor bash tests: storage-compatibility (#7299)
* Add e2e test for compatibility
1 parent 8dd915c commit c3ca182

8 files changed

Lines changed: 156 additions & 167 deletions

File tree

.github/workflows/integration-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ jobs:
257257
load: true
258258
build-args: |
259259
PROFILE=ci
260+
FEATURES=data-consistency-check
260261
- name: Verify Docker image
261262
run: docker images | grep qdrant
262263
- name: Run e2e tests

.github/workflows/storage-compat.yml

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
# Storage compatibility test
1+
# Data compatibility test
22

33
In order to detect quickly breakage of storage compatibility, we check that the current code understands the storage format from the latest stable release.
44

55
To not burden the git repository with tracking the large binary files, we are pushing storage archives to our [GCP bucket](https://storage.googleapis.com/qdrant-backward-compatibility/).
66

7-
As features are added, the storage format may change. This means updating the reference storage data and snapshot. This is done by running the `gen_storage_compat_data.sh` script.
7+
As features are added, the storage format may change. This means updating the reference storage data and snapshot. This is done by running the [gen_storage_compat_data.sh](test_data/compatibility/gen_storage_compat_data.sh) script.
88

99
## Regenerate storage data
1010

1111
Follow those steps to recreate the reference storage data and snapshot.
1212

13-
1. run `./tests/storage-compat/gen_storage_compat_data.sh`
13+
1. run `PROJECT_ROOT/tests/e2e_tests/test_data/compatibility/gen_storage_compat_data.sh`
1414
2. make sure to pick the right version when asked for which system generated the files
1515
3. push the new archives to the GCP bucket (ask for the credentials if you don't have them)

tests/storage-compat/gen_storage_compat_data.sh renamed to tests/e2e_tests/test_data/compatibility/gen_storage_compat_data.sh

File renamed without changes.
File renamed without changes.
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import uuid
2+
3+
import pytest
4+
import requests
5+
from pathlib import Path
6+
7+
from e2e_tests.conftest import QdrantContainerConfig
8+
from e2e_tests.client_utils import ClientUtils
9+
from e2e_tests.utils import extract_archive
10+
11+
12+
class TestStorageCompatibility:
13+
"""Test storage and snapshot compatibility with defined previous Qdrant versions."""
14+
15+
PREV_PATCH_VERSION = "v1.15.4"
16+
PREV_MINOR_VERSION = "v1.14.0" # skipping v1.14.1 as it contains a known data corruption (https://github.com/qdrant/qdrant/pull/6916)
17+
18+
EXPECTED_COLLECTIONS = [
19+
"test_collection_vector_memory",
20+
"test_collection_vector_on_disk",
21+
"test_collection_vector_on_disk_threshold",
22+
"test_collection_scalar_int8",
23+
"test_collection_product_x64",
24+
"test_collection_product_x32",
25+
"test_collection_product_x16",
26+
"test_collection_product_x8",
27+
"test_collection_binary",
28+
"test_collection_mmap_field_index",
29+
"test_collection_vector_datatype_u8",
30+
"test_collection_vector_datatype_f16"
31+
]
32+
33+
@staticmethod
34+
def _download_compatibility_data(version: str, storage_test_dir: Path):
35+
"""Download compatibility data for a specific version."""
36+
# Use test-specific filename to avoid conflicts in parallel execution
37+
test_id = str(uuid.uuid4())[:8]
38+
compatibility_file = storage_test_dir / f"compatibility_{version}_{test_id}.tar"
39+
40+
url = f"https://storage.googleapis.com/qdrant-backward-compatibility/compatibility-{version}.tar"
41+
42+
print(f"Downloading compatibility data for {version}...")
43+
44+
try:
45+
with requests.get(url, stream=True, timeout=(10, 300)) as response:
46+
response.raise_for_status()
47+
with open(compatibility_file, 'wb') as f:
48+
for chunk in response.iter_content(chunk_size=1024 * 1024):
49+
if chunk:
50+
f.write(chunk)
51+
except requests.exceptions.RequestException as e:
52+
pytest.skip(f"Could not download compatibility data for {version}: {e}")
53+
54+
return compatibility_file
55+
56+
@staticmethod
57+
def _extract_storage_data(compatibility_file: Path, storage_test_dir: Path):
58+
"""Extract storage data from compatibility archive."""
59+
extract_archive(compatibility_file, storage_test_dir, cleanup_archive=True)
60+
61+
# Extract nested storage archive
62+
storage_archive = storage_test_dir / "storage.tar.bz2"
63+
if storage_archive.exists():
64+
extract_archive(storage_archive, storage_test_dir, cleanup_archive=True)
65+
66+
return storage_test_dir / "storage"
67+
68+
@staticmethod
69+
def _extract_snapshot_data(storage_test_dir: Path):
70+
"""Extract snapshot data."""
71+
snapshot_gz = storage_test_dir / "full-snapshot.snapshot.gz"
72+
73+
if snapshot_gz.exists():
74+
extract_archive(snapshot_gz, storage_test_dir, cleanup_archive=True)
75+
return storage_test_dir / "full-snapshot.snapshot"
76+
77+
return None
78+
79+
@staticmethod
80+
def _check_collections(host: str, port: int) -> bool:
81+
"""Check that all collections are loaded properly."""
82+
client = ClientUtils(host=host, port=port)
83+
84+
try:
85+
collections = client.list_collections_names()
86+
except Exception as e:
87+
print(f"Error listing collections: {e}")
88+
return False
89+
90+
expected = set(TestStorageCompatibility.EXPECTED_COLLECTIONS)
91+
found = set(collections)
92+
missing = expected - found
93+
if missing:
94+
print(f"Missing expected collections: {sorted(missing)}")
95+
return False
96+
97+
for collection in expected:
98+
try:
99+
collection_info = client.get_collection_info_dict(collection)
100+
if collection_info["status"] != "ok":
101+
print(f"Collection {collection} returned status {collection_info['status']}")
102+
return False
103+
except Exception as error:
104+
print(f"Failed to get collection info for {collection}: {error}")
105+
return False
106+
return True
107+
108+
109+
@pytest.mark.parametrize("version", [PREV_PATCH_VERSION, PREV_MINOR_VERSION])
110+
def test_storage_compatibility(self, docker_client, qdrant_image, temp_storage_dir, version, qdrant_container_factory):
111+
"""Test storage compatibility with previous versions."""
112+
compatibility_file = self._download_compatibility_data(version, temp_storage_dir)
113+
storage_dir = self._extract_storage_data(compatibility_file, temp_storage_dir)
114+
115+
config = QdrantContainerConfig(
116+
volumes={str(storage_dir): {"bind": "/qdrant/storage", "mode": "rw"}},
117+
exit_on_error=False
118+
)
119+
container_info = qdrant_container_factory(config)
120+
121+
client = ClientUtils(host=container_info.host, port=container_info.http_port)
122+
if not client.wait_for_server():
123+
pytest.fail(f"Server failed to start for {version}")
124+
125+
if not self._check_collections(container_info.host, container_info.http_port):
126+
pytest.fail(f"Storage compatibility failed for {version}")
127+
128+
@pytest.mark.parametrize("version", [PREV_PATCH_VERSION, PREV_MINOR_VERSION])
129+
def test_snapshot_compatibility(self, docker_client, qdrant_image, temp_storage_dir, version, qdrant_container_factory):
130+
"""Test snapshot recovery compatibility with previous versions."""
131+
compatibility_file = self._download_compatibility_data(version, temp_storage_dir)
132+
self._extract_storage_data(compatibility_file, temp_storage_dir)
133+
snapshot_file = self._extract_snapshot_data(temp_storage_dir)
134+
135+
if not snapshot_file:
136+
pytest.fail(f"No snapshot file found for {version}")
137+
138+
config = QdrantContainerConfig(
139+
volumes={str(snapshot_file): {"bind": "/qdrant/snapshot.snapshot", "mode": "ro"}},
140+
command=["./qdrant", "--storage-snapshot", "/qdrant/snapshot.snapshot"],
141+
exit_on_error=False
142+
)
143+
container_info = qdrant_container_factory(config)
144+
145+
client = ClientUtils(host=container_info.host, port=container_info.http_port)
146+
if not client.wait_for_server():
147+
pytest.fail(f"Server failed to start from snapshot for {version}")
148+
149+
if not self._check_collections(container_info.host, container_info.http_port):
150+
pytest.fail(f"Snapshot compatibility failed for {version}")

tests/e2e_tests/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,13 +230,13 @@ def extract_archive(archive_file: Path, extract_to: Path, cleanup_archive: bool
230230
elif file_name.endswith(('.tar.xz', '.tar.gz', '.tar.bz2', '.tgz', '.tbz2')):
231231
# Handle compressed tar files
232232
with tarfile.open(archive_file, 'r:*') as tar:
233-
tar.extractall(path=extract_to)
233+
tar.extractall(path=extract_to, filter='data')
234234
print(f"Extracted {archive_file} to {extract_to}")
235235

236236
elif file_name.endswith('.tar'):
237237
# Handle uncompressed tar files
238238
with tarfile.open(archive_file, 'r:') as tar:
239-
tar.extractall(path=extract_to)
239+
tar.extractall(path=extract_to, filter='data')
240240
print(f"Extracted {archive_file} to {extract_to}")
241241

242242
elif file_name.endswith('.zip'):

tests/storage-compat/storage-compatibility.sh

Lines changed: 0 additions & 128 deletions
This file was deleted.

0 commit comments

Comments
 (0)