fix(us-020): silence BrokenPipeError in tracker _send_json; deterministic node IDs; HF model coverage
- Wrap wfile.write in _TrackerHandler._send_json with except BrokenPipeError
- Replace uuid4 node IDs with deterministic SHA-256 hash of endpoint+model+shards
so nodes keep the same ID on re-registration after tracker restart
- /v1/models now lists HF-repo models (not just preset models)
- /v1/coverage/{model} now resolves HF repos, not just preset names
- /v1/route response includes node_id alongside endpoint
- startup.py exposes tracker_node_id on node object and prints it in dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,69 @@ def _get_json(url: str) -> dict:
|
||||
return json.loads(r.read())
|
||||
|
||||
|
||||
def test_tracker_send_json_ignores_broken_pipe_after_client_disconnect():
|
||||
"""A disconnected client must not dump a BrokenPipe traceback from the tracker."""
|
||||
from meshnet_tracker.server import _TrackerHandler
|
||||
|
||||
class BrokenPipeWriter:
|
||||
def write(self, _body):
|
||||
raise BrokenPipeError
|
||||
|
||||
class DummyHandler:
|
||||
wfile = BrokenPipeWriter()
|
||||
|
||||
def send_response(self, _status):
|
||||
pass
|
||||
|
||||
def send_header(self, _name, _value):
|
||||
pass
|
||||
|
||||
def end_headers(self):
|
||||
pass
|
||||
|
||||
_TrackerHandler._send_json(DummyHandler(), 200, {"ok": True})
|
||||
|
||||
|
||||
def test_tracker_registration_node_id_includes_wallet_prefix_and_stable_suffix():
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
wallet = "7j77FsPY1evV8tuf4Z73AVrWwxBEW1pvKwi4EvcRD3g"
|
||||
try:
|
||||
first = _post_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/nodes/register",
|
||||
{"endpoint": "http://127.0.0.1:9100", "model": "Qwen2.5-0.5B-Instruct",
|
||||
"hf_repo": "Qwen/Qwen2.5-0.5B-Instruct", "num_layers": 24,
|
||||
"shard_start": 0, "shard_end": 21, "wallet_address": wallet,
|
||||
"hardware_profile": {}, "score": 1.0},
|
||||
)
|
||||
second = _post_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/nodes/register",
|
||||
{"endpoint": "http://127.0.0.1:9100", "model": "Qwen2.5-0.5B-Instruct",
|
||||
"hf_repo": "Qwen/Qwen2.5-0.5B-Instruct", "num_layers": 24,
|
||||
"shard_start": 0, "shard_end": 21, "wallet_address": wallet,
|
||||
"hardware_profile": {}, "score": 1.0},
|
||||
)
|
||||
different_endpoint = _post_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/nodes/register",
|
||||
{"endpoint": "http://127.0.0.1:9101", "model": "Qwen2.5-0.5B-Instruct",
|
||||
"hf_repo": "Qwen/Qwen2.5-0.5B-Instruct", "num_layers": 24,
|
||||
"shard_start": 20, "shard_end": 23, "wallet_address": wallet,
|
||||
"hardware_profile": {}, "score": 1.0},
|
||||
)
|
||||
|
||||
assert first["node_id"].startswith("7j77FsPY-")
|
||||
assert second["node_id"] == first["node_id"]
|
||||
assert different_endpoint["node_id"].startswith("7j77FsPY-")
|
||||
assert different_endpoint["node_id"] != first["node_id"]
|
||||
|
||||
route_resp = _get_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/route?model=Qwen/Qwen2.5-0.5B-Instruct"
|
||||
)
|
||||
assert route_resp["nodes"][0]["node_id"] == first["node_id"]
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def test_tracker_node_registration():
|
||||
"""A node can register with the tracker and receives a node_id."""
|
||||
tracker = TrackerServer()
|
||||
@@ -131,6 +194,64 @@ def test_tracker_coverage_endpoint_reports_uncovered_ranges():
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def test_tracker_coverage_endpoint_accepts_registered_hf_repo_or_short_name():
|
||||
"""Coverage endpoint supports real HF models registered outside preset catalog."""
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
_post_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/nodes/register",
|
||||
{"endpoint": "http://127.0.0.1:9101", "model": "Qwen2.5-0.5B-Instruct",
|
||||
"hf_repo": "Qwen/Qwen2.5-0.5B-Instruct", "num_layers": 24,
|
||||
"shard_start": 0, "shard_end": 21, "hardware_profile": {}, "score": 1.0},
|
||||
)
|
||||
_post_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/nodes/register",
|
||||
{"endpoint": "http://127.0.0.1:9102", "model": "Qwen2.5-0.5B-Instruct",
|
||||
"hf_repo": "Qwen/Qwen2.5-0.5B-Instruct", "num_layers": 24,
|
||||
"shard_start": 20, "shard_end": 23, "hardware_profile": {}, "score": 1.0},
|
||||
)
|
||||
|
||||
by_repo = _get_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/coverage/Qwen/Qwen2.5-0.5B-Instruct"
|
||||
)
|
||||
by_short_name = _get_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/coverage/Qwen2.5-0.5B-Instruct"
|
||||
)
|
||||
|
||||
assert by_repo["model"] == "Qwen/Qwen2.5-0.5B-Instruct"
|
||||
assert by_repo["coverage"] == [
|
||||
{"start_layer": 0, "end_layer": 19, "node_count": 1},
|
||||
{"start_layer": 20, "end_layer": 21, "node_count": 2},
|
||||
{"start_layer": 22, "end_layer": 23, "node_count": 1},
|
||||
]
|
||||
assert by_short_name["coverage"] == by_repo["coverage"]
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def test_tracker_models_endpoint_lists_registered_hf_repo_and_short_name_alias():
|
||||
tracker = TrackerServer()
|
||||
tracker_port = tracker.start()
|
||||
try:
|
||||
_post_json(
|
||||
f"http://127.0.0.1:{tracker_port}/v1/nodes/register",
|
||||
{"endpoint": "http://127.0.0.1:9111", "model": "Qwen2.5-0.5B-Instruct",
|
||||
"hf_repo": "Qwen/Qwen2.5-0.5B-Instruct", "num_layers": 24,
|
||||
"shard_start": 0, "shard_end": 23, "hardware_profile": {}, "score": 1.0},
|
||||
)
|
||||
|
||||
models_resp = _get_json(f"http://127.0.0.1:{tracker_port}/v1/models")
|
||||
|
||||
model = next(item for item in models_resp["data"] if item["id"] == "Qwen/Qwen2.5-0.5B-Instruct")
|
||||
assert model["name"] == "Qwen2.5-0.5B-Instruct"
|
||||
assert model["hf_repo"] == "Qwen/Qwen2.5-0.5B-Instruct"
|
||||
assert model["aliases"] == ["Qwen/Qwen2.5-0.5B-Instruct", "Qwen2.5-0.5B-Instruct"]
|
||||
assert model["shard_coverage_percentage"] == 100.0
|
||||
finally:
|
||||
tracker.stop()
|
||||
|
||||
|
||||
def test_tracker_auto_assigns_new_node_to_uncovered_range_first():
|
||||
"""Capability-driven registration fills the first uncovered layer gap."""
|
||||
tracker = TrackerServer(model_presets={
|
||||
|
||||
Reference in New Issue
Block a user