Files
qdrant/tests/consensus_tests/fixtures.py
Tim Visée 463a305404 Add routing token for deterministic read routes (#9338)
* Add routing token structure

* Implement routing token in read operation executor as per design doc

* Add TODO to glue routing token to user requests

* Implement routing header for REST API

* Source routing token from request, not from JWT token

* Implement routing token in gRPC API

* Add test

* Review remarks

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Use lower case header name to prevent panic

* Rename header to X-Qdrant-Route-Affinity

* Assert routing consistency in test on all peers

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-06-17 10:59:46 +02:00

274 lines
7.4 KiB
Python

import random
from typing import List
import requests
from consensus_tests.assertions import assert_http_ok
CITIES = ["London", "New York", "Paris", "Tokyo", "Berlin", "Rome", "Madrid", "Moscow"]
# dense vector sizing
DENSE_VECTOR_SIZE = 4
# sparse vector sizing
SPARSE_VECTOR_SIZE = 1000
SPARSE_VECTOR_DENSITY = 0.1
# Generate a random dense vector
def random_dense_vector():
return [random.random() for _ in range(DENSE_VECTOR_SIZE)]
# Generate a random sparse vector
def random_sparse_vector():
num_non_zero = int(SPARSE_VECTOR_SIZE * SPARSE_VECTOR_DENSITY)
indices: List[int] = random.sample(range(SPARSE_VECTOR_SIZE), num_non_zero)
values: List[float] = [round(random.random(), 6) for _ in range(num_non_zero)]
return {"indices": indices, "values": values}
def upsert_points(
peer_url,
points,
collection_name="test_collection",
wait="true",
ordering="weak",
shard_key=None,
headers={},
) -> requests.Response:
return requests.put(
f"{peer_url}/collections/{collection_name}/points?wait={wait}&ordering={ordering}",
json={
"points": points,
"shard_key": shard_key,
},
headers=headers,
)
def update_points_vector(
peer_url,
points,
collection_name="test_collection",
wait="true",
):
r_batch = requests.put(
f"{peer_url}/collections/{collection_name}/points/vectors?wait={wait}",
json={
"points": [{"id": x, "vector": {"": random_dense_vector()}} for x in points],
},
)
assert_http_ok(r_batch)
return r_batch.json()
def update_points_payload(
peer_url,
points,
collection_name="test_collection",
wait="true",
shard_key=None,
):
r_batch = requests.post(
f"{peer_url}/collections/{collection_name}/points/payload?wait={wait}",
json={
"points": points,
"payload": {"city": random.choice(CITIES)},
"shard_key": shard_key,
},
)
assert_http_ok(r_batch)
return r_batch.json()
def upsert_random_points(
peer_url,
num,
collection_name="test_collection",
fail_on_error=True,
offset=0,
batch_size=None,
wait="true",
ordering="weak",
with_sparse_vector=True,
shard_key=None,
num_cities=None,
headers={},
extra_payload=None,
timeout=None,
):
extra_payload = extra_payload or {}
def get_vector():
# Create points in first peer's collection
vector = {
"": random_dense_vector(),
}
if with_sparse_vector:
vector["sparse-text"] = random_sparse_vector()
return vector
while num > 0:
size = num if batch_size is None else min(num, batch_size)
r_batch = requests.put(
f"{peer_url}/collections/{collection_name}/points?wait={wait}&ordering={ordering}",
json={
"points": [
{
"id": i + offset,
"vector": get_vector(),
"payload": {
**extra_payload,
"city": random.choice(CITIES[:num_cities]) if num_cities is not None else random.choice(CITIES)
},
}
for i in range(size)
],
"shard_key": shard_key,
},
headers=headers,
timeout=timeout,
)
if fail_on_error:
assert_http_ok(r_batch)
num -= size
offset += size
def create_collection(
peer_url,
collection="test_collection",
shard_number=1,
replication_factor=1,
write_consistency_factor=1,
timeout=10,
sharding_method=None,
indexing_threshold=20000,
headers={},
strict_mode=None,
sparse_vectors=True,
default_segment_number=None,
on_disk_payload=None,
read_fan_out_factor=None,
fail_on_error=True,
):
payload = {
"vectors": {"size": DENSE_VECTOR_SIZE, "distance": "Dot"},
"shard_number": shard_number,
"replication_factor": replication_factor,
"write_consistency_factor": write_consistency_factor,
"read_fan_out_factor": read_fan_out_factor,
"sharding_method": sharding_method,
"optimizers_config": {
"indexing_threshold": indexing_threshold,
"default_segment_number": default_segment_number,
},
"strict_mode_config": strict_mode,
"on_disk_payload": on_disk_payload,
}
if sparse_vectors:
payload["sparse_vectors"] = {"sparse-text": {}}
# Create collection in peer_url
r_batch = requests.put(
f"{peer_url}/collections/{collection}?timeout={timeout}",
json=payload,
headers=headers,
)
if fail_on_error:
assert_http_ok(r_batch)
def drop_collection(peer_url, collection="test_collection", timeout=10, headers={}):
# Delete collection in peer_url
r_delete = requests.delete(
f"{peer_url}/collections/{collection}?timeout={timeout}", headers=headers
)
assert_http_ok(r_delete)
def create_field_index(
peer_url,
collection="test_collection",
field_name="city",
field_schema="keyword",
headers={},
):
# Create field index in peer_url
r_batch = requests.put(
f"{peer_url}/collections/{collection}/index",
json={
"field_name": field_name,
"field_schema": field_schema,
},
headers=headers,
params={"wait": "true"}
)
assert_http_ok(r_batch)
def search(peer_url, vector, city, collection="test_collection"):
q = {
"vector": vector,
"top": 10,
"with_vector": False,
"with_payload": True,
"filter": {"must": [{"key": "city", "match": {"value": city}}]},
}
r_search = requests.post(f"{peer_url}/collections/{collection}/points/search", json=q)
assert_http_ok(r_search)
return r_search.json()["result"]
def scroll(peer_url, city, collection="test_collection"):
q = {
"with_vector": False,
"with_payload": True,
"filter": {"must": [{"key": "city", "match": {"value": city}}]},
}
r_search = requests.post(f"{peer_url}/collections/{collection}/points/scroll", json=q)
assert_http_ok(r_search)
return r_search.json()["result"]["points"]
def count_counts(peer_url, collection="test_collection"):
r_search = requests.post(
f"{peer_url}/collections/{collection}/points/count",
json={
"exact": True,
},
)
assert_http_ok(r_search)
return r_search.json()["result"]["count"]
def set_strict_mode(peer_id, collection_name, strict_mode_config):
requests.patch(
f"{peer_id}/collections/{collection_name}",
json={
"strict_mode_config": strict_mode_config,
},
).raise_for_status()
def get_telemetry_hw_info(peer_url, collection):
r_search = requests.get(
f"{peer_url}/telemetry", params="details_level=3"
)
assert_http_ok(r_search)
hw = r_search.json()["result"]["hardware"]["collection_data"]
if collection in hw:
return hw[collection]
else:
return None
def get_telemetry_collections(peer_url):
r_search = requests.get(
f"{peer_url}/telemetry", params="details_level=3"
)
assert_http_ok(r_search)
return r_search.json()["result"]['collections']['collections']