lmcache-kvblockd 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lmcache_kvblockd-0.2.0/.gitignore +56 -0
- lmcache_kvblockd-0.2.0/PKG-INFO +88 -0
- lmcache_kvblockd-0.2.0/README.md +70 -0
- lmcache_kvblockd-0.2.0/pyproject.toml +32 -0
- lmcache_kvblockd-0.2.0/src/lmcache_kvblockd/__init__.py +1 -0
- lmcache_kvblockd-0.2.0/src/lmcache_kvblockd/adapter.py +127 -0
- lmcache_kvblockd-0.2.0/src/lmcache_kvblockd/connector.py +539 -0
- lmcache_kvblockd-0.2.0/src/lmcache_kvblockd/meta.py +77 -0
- lmcache_kvblockd-0.2.0/tests/test_connector_unit.py +251 -0
- lmcache_kvblockd-0.2.0/tests/test_fleet_adapter_urls.py +328 -0
- lmcache_kvblockd-0.2.0/tests/test_fleet_allocator_serialize.py +398 -0
- lmcache_kvblockd-0.2.0/tests/test_fleet_connector_fakeclient.py +891 -0
- lmcache_kvblockd-0.2.0/tests/test_fleet_meta_boundary.py +154 -0
- lmcache_kvblockd-0.2.0/tests/test_health_monitor_contract.py +55 -0
- lmcache_kvblockd-0.2.0/tests/test_initialization_deadline.py +102 -0
- lmcache_kvblockd-0.2.0/tests/test_initialization_ownership.py +142 -0
- lmcache_kvblockd-0.2.0/tests/test_invalid_metadata_cleanup.py +79 -0
- lmcache_kvblockd-0.2.0/tests/test_live_metadata_recovery.py +150 -0
- lmcache_kvblockd-0.2.0/tests/test_lmcache_registration.py +142 -0
- lmcache_kvblockd-0.2.0/tests/test_meta.py +51 -0
- lmcache_kvblockd-0.2.0/tests/test_operation_deadline.py +244 -0
- lmcache_kvblockd-0.2.0/tests/test_pin_lease_contract.py +40 -0
- lmcache_kvblockd-0.2.0/tests/test_real_memory_contract.py +403 -0
- lmcache_kvblockd-0.2.0/tests/test_real_retrieval_contract.py +152 -0
- lmcache_kvblockd-0.2.0/tests/test_receive_ownership.py +273 -0
- lmcache_kvblockd-0.2.0/tests/test_sync_lookup_contract.py +40 -0
- lmcache_kvblockd-0.2.0/tests/test_tag_identity_contract.py +56 -0
- lmcache_kvblockd-0.2.0/tests/test_unsafe_copy_path.py +54 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# ── PRIVATE RESEARCH VAULT — never commit ─────────────────────────
|
|
2
|
+
# All competitive research, strategy, business plans, drafts, and course material.
|
|
3
|
+
research/
|
|
4
|
+
|
|
5
|
+
# OS noise
|
|
6
|
+
.DS_Store
|
|
7
|
+
|
|
8
|
+
# Build artifacts
|
|
9
|
+
bin/
|
|
10
|
+
dist/
|
|
11
|
+
*.prof
|
|
12
|
+
*.test
|
|
13
|
+
testdata/corpus-large/
|
|
14
|
+
|
|
15
|
+
# External build dependencies fetched at build time (never vendored):
|
|
16
|
+
# ai-dynamo/nixl checkout for the adapters/nixl plugin (see its README).
|
|
17
|
+
build-deps/
|
|
18
|
+
adapters/nixl/build/
|
|
19
|
+
|
|
20
|
+
# Local env
|
|
21
|
+
.env
|
|
22
|
+
|
|
23
|
+
# Soak ARTIFACTS (logs, JSONL, pprof snapshots) — never the driver source
|
|
24
|
+
/soak
|
|
25
|
+
/soak.jsonl
|
|
26
|
+
bench/rigs/aws-soak/results/
|
|
27
|
+
bench/rigs/aws-dramgate/allocs-*.pb.gz
|
|
28
|
+
|
|
29
|
+
# Claude session rules — private, never push
|
|
30
|
+
CLAUDE.md
|
|
31
|
+
|
|
32
|
+
# Compiled command binaries accidentally built into the tree
|
|
33
|
+
/xferspike
|
|
34
|
+
/kvblockd
|
|
35
|
+
/kvbctl
|
|
36
|
+
cmd/xferspike/xferspike
|
|
37
|
+
|
|
38
|
+
# Python
|
|
39
|
+
__pycache__/
|
|
40
|
+
*.pyc
|
|
41
|
+
.pytest_cache/
|
|
42
|
+
.ruff_cache/
|
|
43
|
+
*.egg-info/
|
|
44
|
+
python/**/dist/
|
|
45
|
+
|
|
46
|
+
# mutation-testing artifacts
|
|
47
|
+
report.json
|
|
48
|
+
bench/rigs/*/.rig-state
|
|
49
|
+
|
|
50
|
+
# locally built bench binaries
|
|
51
|
+
/kvbench
|
|
52
|
+
/crash
|
|
53
|
+
/getbench
|
|
54
|
+
internal/store/modeltest/testdata/rapid/
|
|
55
|
+
bench/rigs/aws-s3soak/results/
|
|
56
|
+
.claude/
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lmcache-kvblockd
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: LMCache remote-storage backend backed by kvblockd
|
|
5
|
+
Project-URL: Homepage, https://kvstash.com
|
|
6
|
+
Project-URL: Documentation, https://kvstash.com/integrations/lmcache/
|
|
7
|
+
Project-URL: Get the daemon, https://kvstash.com/docs/install/
|
|
8
|
+
Author-email: Rudray Mehra <rudraymehra@gmail.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: kvblockd>=0.2.0
|
|
12
|
+
Requires-Dist: lmcache<0.6,>=0.5.1
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
16
|
+
Requires-Dist: torch; extra == 'test'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# lmcache-kvblockd
|
|
20
|
+
|
|
21
|
+
[LMCache](https://github.com/LMCache/LMCache) remote storage plugin for
|
|
22
|
+
[KVStash](https://kvstash.com). vLLM deployments that already run LMCache can
|
|
23
|
+
keep KV in the KVStash daemon (`kvblockd://` scheme) and reload it after a
|
|
24
|
+
restart or from another server.
|
|
25
|
+
|
|
26
|
+
- Get the daemon (free download): https://kvstash.com/docs/install/
|
|
27
|
+
- Full guide: https://kvstash.com/integrations/lmcache/
|
|
28
|
+
|
|
29
|
+
Linux x86-64 only (LMCache ships manylinux wheels only).
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pip install lmcache-kvblockd
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Configure
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
PYTHONHASHSEED=0 vllm serve MODEL --kv-transfer-config '{
|
|
41
|
+
"kv_connector": "LMCacheConnectorV1", "kv_role": "kv_both",
|
|
42
|
+
"kv_connector_extra_config": {
|
|
43
|
+
"lmcache.remote_storage_plugins": "kvblockd",
|
|
44
|
+
"lmcache.local_cpu": true,
|
|
45
|
+
"lmcache.chunk_size": 256,
|
|
46
|
+
"lmcache.extra_config": {
|
|
47
|
+
"kvblockd_token": "YOUR_TOKEN",
|
|
48
|
+
"remote_storage_plugin.kvblockd.module_path": "lmcache_kvblockd.adapter",
|
|
49
|
+
"remote_storage_plugin.kvblockd.class_name": "KvblockdConnectorAdapter",
|
|
50
|
+
"remote_storage_plugin.kvblockd.url": "kvblockd://STORE_HOST:9440?namespace=lmcache&streams=4"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}'
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- The address goes in `remote_storage_plugin.kvblockd.url`. LMCache ignores
|
|
57
|
+
`remote_url` when `remote_storage_plugins` is set, and setting both creates
|
|
58
|
+
a second backend that doubles every write.
|
|
59
|
+
- Set `PYTHONHASHSEED=0` on every worker. LMCache's chunk keys depend on it,
|
|
60
|
+
so workers with different seeds cannot share cache.
|
|
61
|
+
- Use a separate namespace per model revision, dtype and cache layout.
|
|
62
|
+
|
|
63
|
+
## LMCache's CUDA extension is required on vLLM 0.33 and newer
|
|
64
|
+
|
|
65
|
+
Without its compiled `lmcache.cuda_ops` extension, LMCache copies KV between
|
|
66
|
+
GPU and CPU with a torch fallback that does not handle the fused K/V layout of
|
|
67
|
+
vLLM 0.33. The bytes it hands the plugin are then not the KV, and a reload
|
|
68
|
+
gives wrong answers. When CUDA is present, the extension does not import and
|
|
69
|
+
vLLM is 0.33 or newer, the plugin refuses to start and says why; LMCache keeps
|
|
70
|
+
serving without the remote tier. Check before you deploy:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
python -c "import lmcache.cuda_ops"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If that fails, install an LMCache build compiled for your torch version.
|
|
77
|
+
|
|
78
|
+
## Behaviour notes
|
|
79
|
+
|
|
80
|
+
- Cache tags must have string names and values without `@` or `%` (LMCache's
|
|
81
|
+
text keys do not escape them). Unsupported tags become cache misses.
|
|
82
|
+
- GET and lookup deadlines include queueing and connection set-up.
|
|
83
|
+
- Lookup with `pin=True` reports only the leading run of keys whose
|
|
84
|
+
time-limited leases were acknowledged. A lease can expire; LMCache's full
|
|
85
|
+
asynchronous pin/unpin lifecycle is not implemented.
|
|
86
|
+
- Records with malformed headers or contradictory tensor sizes are removed so
|
|
87
|
+
they can be written again. Other read problems stay misses without deleting
|
|
88
|
+
valid records. Do not share a namespace between incompatible writers.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# lmcache-kvblockd
|
|
2
|
+
|
|
3
|
+
[LMCache](https://github.com/LMCache/LMCache) remote storage plugin for
|
|
4
|
+
[KVStash](https://kvstash.com). vLLM deployments that already run LMCache can
|
|
5
|
+
keep KV in the KVStash daemon (`kvblockd://` scheme) and reload it after a
|
|
6
|
+
restart or from another server.
|
|
7
|
+
|
|
8
|
+
- Get the daemon (free download): https://kvstash.com/docs/install/
|
|
9
|
+
- Full guide: https://kvstash.com/integrations/lmcache/
|
|
10
|
+
|
|
11
|
+
Linux x86-64 only (LMCache ships manylinux wheels only).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pip install lmcache-kvblockd
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Configure
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
PYTHONHASHSEED=0 vllm serve MODEL --kv-transfer-config '{
|
|
23
|
+
"kv_connector": "LMCacheConnectorV1", "kv_role": "kv_both",
|
|
24
|
+
"kv_connector_extra_config": {
|
|
25
|
+
"lmcache.remote_storage_plugins": "kvblockd",
|
|
26
|
+
"lmcache.local_cpu": true,
|
|
27
|
+
"lmcache.chunk_size": 256,
|
|
28
|
+
"lmcache.extra_config": {
|
|
29
|
+
"kvblockd_token": "YOUR_TOKEN",
|
|
30
|
+
"remote_storage_plugin.kvblockd.module_path": "lmcache_kvblockd.adapter",
|
|
31
|
+
"remote_storage_plugin.kvblockd.class_name": "KvblockdConnectorAdapter",
|
|
32
|
+
"remote_storage_plugin.kvblockd.url": "kvblockd://STORE_HOST:9440?namespace=lmcache&streams=4"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- The address goes in `remote_storage_plugin.kvblockd.url`. LMCache ignores
|
|
39
|
+
`remote_url` when `remote_storage_plugins` is set, and setting both creates
|
|
40
|
+
a second backend that doubles every write.
|
|
41
|
+
- Set `PYTHONHASHSEED=0` on every worker. LMCache's chunk keys depend on it,
|
|
42
|
+
so workers with different seeds cannot share cache.
|
|
43
|
+
- Use a separate namespace per model revision, dtype and cache layout.
|
|
44
|
+
|
|
45
|
+
## LMCache's CUDA extension is required on vLLM 0.33 and newer
|
|
46
|
+
|
|
47
|
+
Without its compiled `lmcache.cuda_ops` extension, LMCache copies KV between
|
|
48
|
+
GPU and CPU with a torch fallback that does not handle the fused K/V layout of
|
|
49
|
+
vLLM 0.33. The bytes it hands the plugin are then not the KV, and a reload
|
|
50
|
+
gives wrong answers. When CUDA is present, the extension does not import and
|
|
51
|
+
vLLM is 0.33 or newer, the plugin refuses to start and says why; LMCache keeps
|
|
52
|
+
serving without the remote tier. Check before you deploy:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
python -c "import lmcache.cuda_ops"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If that fails, install an LMCache build compiled for your torch version.
|
|
59
|
+
|
|
60
|
+
## Behaviour notes
|
|
61
|
+
|
|
62
|
+
- Cache tags must have string names and values without `@` or `%` (LMCache's
|
|
63
|
+
text keys do not escape them). Unsupported tags become cache misses.
|
|
64
|
+
- GET and lookup deadlines include queueing and connection set-up.
|
|
65
|
+
- Lookup with `pin=True` reports only the leading run of keys whose
|
|
66
|
+
time-limited leases were acknowledged. A lease can expire; LMCache's full
|
|
67
|
+
asynchronous pin/unpin lifecycle is not implemented.
|
|
68
|
+
- Records with malformed headers or contradictory tensor sizes are removed so
|
|
69
|
+
they can be written again. Other read problems stay misses without deleting
|
|
70
|
+
valid records. Do not share a namespace between incompatible writers.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lmcache-kvblockd"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "LMCache remote-storage backend backed by kvblockd"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [{ name = "Rudray Mehra", email = "rudraymehra@gmail.com" }]
|
|
13
|
+
dependencies = [
|
|
14
|
+
"kvblockd>=0.2.0",
|
|
15
|
+
"lmcache>=0.5.1,<0.6",
|
|
16
|
+
"numpy", # zero-copy MemoryObj views via tensor.numpy() (torch does not pull it)
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://kvstash.com"
|
|
21
|
+
Documentation = "https://kvstash.com/integrations/lmcache/"
|
|
22
|
+
"Get the daemon" = "https://kvstash.com/docs/install/"
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
test = ["pytest>=8", "torch"]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/lmcache_kvblockd"]
|
|
29
|
+
|
|
30
|
+
[tool.ruff]
|
|
31
|
+
line-length = 100
|
|
32
|
+
target-version = "py310"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""LMCache remote-storage backend backed by kvblockd."""
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""KvblockdConnectorAdapter — registers the kvblockd:// scheme with LMCache's
|
|
2
|
+
ConnectorManager (plugin path: remote_storage_plugins + extra_config
|
|
3
|
+
module_path/class_name; see docs/notes/lmcache-head-verify.md).
|
|
4
|
+
|
|
5
|
+
TWO URL shapes must parse, because LMCache 0.5.x dials remote backends two
|
|
6
|
+
different ways (lmcache/v1/storage_backend/remote_backend.py, init_connection):
|
|
7
|
+
|
|
8
|
+
1. legacy `remote_url` backend → url = "kvblockd://host:port?..."
|
|
9
|
+
2. plugin backend (the `remote_storage_plugins` path) → url =
|
|
10
|
+
"plugin://kvblockd" — a VIRTUAL url carrying no endpoint at all.
|
|
11
|
+
|
|
12
|
+
An adapter that only matches kvblockd:// makes the plugin backend fail
|
|
13
|
+
"No adapter found for URL: plugin://kvblockd"; RemoteBackend swallows that
|
|
14
|
+
(connection stays None) and every op silently degrades to a miss — the
|
|
15
|
+
GPU-box zero-bytes failure. For plugin:// the real endpoint comes from
|
|
16
|
+
extra_config["remote_storage_plugin.kvblockd.url"] (falling back to
|
|
17
|
+
config.remote_url when it is a kvblockd:// url). This mirrors how builtin
|
|
18
|
+
adapters (fs_adapter.py) treat plugin:// URLs in lmcache 0.5.1.
|
|
19
|
+
|
|
20
|
+
The LMCache ConnectorAdapter base is resolved at import time; if LMCache is
|
|
21
|
+
absent (CI import checks, or unit tests without lmcache) we fall back to
|
|
22
|
+
`object` so the module still imports and the class still instantiates."""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from .connector import make_connector
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from lmcache.v1.storage_backend.connector import ConnectorAdapter as _Base
|
|
30
|
+
except Exception: # noqa: BLE001 — availability fallback: LMCache not installed — keep the module importable
|
|
31
|
+
_Base = object
|
|
32
|
+
|
|
33
|
+
SCHEME = "kvblockd://"
|
|
34
|
+
PLUGIN_SCHEME = "plugin://"
|
|
35
|
+
PLUGIN_TYPE = "kvblockd"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _plugin_type(name: str) -> str:
|
|
39
|
+
"""Mirror lmcache's extract_plugin_type: '{type}' or '{type}.{instance}'."""
|
|
40
|
+
return name.split(".", 1)[0]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def unsafe_copy_path() -> str | None:
|
|
44
|
+
"""Why storing through this LMCache would write wrong KV, or None.
|
|
45
|
+
|
|
46
|
+
Without its compiled lmcache.cuda_ops extension LMCache copies between GPU
|
|
47
|
+
and CPU with a torch fallback that has no branch for the fused K/V layout
|
|
48
|
+
vLLM 0.33 allocates (LBNHC), so the bytes it hands us are not the KV. The
|
|
49
|
+
daemon never overwrites a stored key, so such blocks would stay wrong
|
|
50
|
+
after LMCache is fixed: refuse instead.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
import torch
|
|
54
|
+
|
|
55
|
+
if not torch.cuda.is_available():
|
|
56
|
+
return None # CPU-only processes (tests, CI) never take the GPU copy path
|
|
57
|
+
except Exception: # noqa: BLE001 - no torch, no GPU copy path
|
|
58
|
+
return None
|
|
59
|
+
try:
|
|
60
|
+
import lmcache.cuda_ops # noqa: F401
|
|
61
|
+
return None
|
|
62
|
+
except ImportError:
|
|
63
|
+
pass
|
|
64
|
+
try:
|
|
65
|
+
from importlib.metadata import version
|
|
66
|
+
|
|
67
|
+
major_minor = tuple(int(x) for x in version("vllm").split(".")[:2])
|
|
68
|
+
except Exception: # noqa: BLE001 - unknown vLLM: keep the old behaviour
|
|
69
|
+
return None
|
|
70
|
+
if major_minor >= (0, 33):
|
|
71
|
+
return ("kvblockd plugin disabled: LMCache's compiled lmcache.cuda_ops extension is not "
|
|
72
|
+
"importable, and its torch fallback copies vLLM >= 0.33's fused K/V layout into the "
|
|
73
|
+
"wrong blocks. Install an LMCache build with its CUDA extension for this torch.")
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class KvblockdConnectorAdapter(_Base):
|
|
78
|
+
"""Matches kvblockd:// and plugin://kvblockd[.instance] URLs and builds a
|
|
79
|
+
KvblockdRemoteConnector."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, schema: str = SCHEME):
|
|
82
|
+
try:
|
|
83
|
+
super().__init__(schema) # ConnectorAdapter(schema) when present
|
|
84
|
+
except TypeError:
|
|
85
|
+
self.schema = schema # object() base: set it ourselves
|
|
86
|
+
|
|
87
|
+
def can_parse(self, url: str) -> bool:
|
|
88
|
+
if url.startswith(SCHEME):
|
|
89
|
+
return True
|
|
90
|
+
if url.startswith(PLUGIN_SCHEME):
|
|
91
|
+
return _plugin_type(url[len(PLUGIN_SCHEME):]) == PLUGIN_TYPE
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
def create_connector(self, context):
|
|
95
|
+
reason = unsafe_copy_path()
|
|
96
|
+
if reason:
|
|
97
|
+
# RemoteBackend.init_connection catches this and runs without the
|
|
98
|
+
# remote tier: nothing reaches the store, so nothing wrong persists.
|
|
99
|
+
raise RuntimeError(reason)
|
|
100
|
+
return make_connector(context, url=self._resolve_url(context))
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _resolve_url(context) -> str:
|
|
104
|
+
"""The real kvblockd:// endpoint for this context.
|
|
105
|
+
|
|
106
|
+
plugin:// URLs are virtual — the endpoint lives in config. Raising here
|
|
107
|
+
is deliberate and SAFE: RemoteBackend.init_connection catches it and
|
|
108
|
+
logs a warning, and a loud misconfiguration beats a silent no-op."""
|
|
109
|
+
url = getattr(context, "url", "") or ""
|
|
110
|
+
if url.startswith(SCHEME):
|
|
111
|
+
return url
|
|
112
|
+
cfg = getattr(context, "config", None)
|
|
113
|
+
extra = getattr(cfg, "extra_config", None) or {}
|
|
114
|
+
pname = getattr(context, "plugin_name", None) or url[len(PLUGIN_SCHEME):] or PLUGIN_TYPE
|
|
115
|
+
# Full instance name first ("kvblockd.primary"), then the bare type.
|
|
116
|
+
for name in dict.fromkeys((pname, _plugin_type(pname))):
|
|
117
|
+
candidate = extra.get(f"remote_storage_plugin.{name}.url")
|
|
118
|
+
if candidate:
|
|
119
|
+
return candidate
|
|
120
|
+
remote_url = getattr(cfg, "remote_url", None)
|
|
121
|
+
if remote_url and remote_url.startswith(SCHEME):
|
|
122
|
+
return remote_url
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"kvblockd plugin selected (url={url!r}) but no endpoint configured: set "
|
|
125
|
+
f"extra_config['remote_storage_plugin.{_plugin_type(pname)}.url'] = "
|
|
126
|
+
f"'kvblockd://HOST:PORT?namespace=...&streams=...'"
|
|
127
|
+
)
|