from __future__ import annotations
import logging
import time
import grpc
from .. import envs
from .. import p2p_pb2, p2p_pb2_grpc
from .payload import tensor_source_metadata
from ..client import MxClientBase
from .source_id import compute_mx_source_id
logger = logging.getLogger("modelexpress.metadata.k8s_service_client")
_DEFAULT_SERVICE_PATTERN = "mx-sources"
_DEFAULT_WORKER_GRPC_PORT = 6555
_DEFAULT_MAX_RETRIES = 5
_DEFAULT_BACKOFF_SECONDS = 0.5
class MxK8sServiceClient(MxClientBase):
REQUIRES_P2P_METADATA = True
def __init__(
self,
worker_rank: int | None = None,
service_pattern: str | None = None,
max_retries: int | None = None,
backoff_seconds: float | None = None,
):
self._worker_rank = worker_rank
self._service_pattern = service_pattern or envs.MX_K8S_SERVICE_PATTERN
env_retries = envs.MX_K8S_SOURCE_RETRIES
self._max_retries = (
max_retries if max_retries is not None
else int(env_retries) if env_retries
else _DEFAULT_MAX_RETRIES
)
env_backoff = envs.MX_K8S_SOURCE_BACKOFF_SECONDS
self._backoff_seconds = (
backoff_seconds if backoff_seconds is not None
else float(env_backoff) if env_backoff
else _DEFAULT_BACKOFF_SECONDS
)
def close(self) -> None:
def worker_rpc_retry_policy(self) -> tuple[int, float]:
return self._max_retries, self._backoff_seconds
def publish_metadata(
self,
identity: "p2p_pb2.SourceIdentity",
worker: "p2p_pb2.WorkerMetadata",
worker_id: str,
) -> str:
self._worker_rank = worker.worker_rank
source_id = compute_mx_source_id(identity)
logger.info(
"MxK8sServiceClient.publish_metadata: "
"computed mx_source_id=%s for worker_rank=%d",
source_id, worker.worker_rank,
)
return source_id
def list_sources(
self,
identity: "p2p_pb2.SourceIdentity | None" = None,
status_filter: "p2p_pb2.SourceStatus | None" = None,
) -> "p2p_pb2.ListSourcesResponse":
if identity is None:
raise ValueError(
"list_sources requires an identity so mx_source_id can "
"be computed locally without a central coordinator"
)
if self._worker_rank is None:
raise RuntimeError(
"MxK8sServiceClient needs a worker_rank before "
"list_sources can resolve the Service endpoint; pass "
"worker_rank to the constructor or call publish_metadata "
"first"
)
source_id = compute_mx_source_id(identity)
ref = p2p_pb2.SourceInstanceRef(
mx_source_id=source_id,
worker_id="",
model_name=identity.model_name,
worker_rank=self._worker_rank,
)
return p2p_pb2.ListSourcesResponse(instances=[ref])
def get_metadata(
self,
mx_source_id: str,
worker_id: str,
) -> "p2p_pb2.GetMetadataResponse":
if self._worker_rank is None:
raise RuntimeError(
"MxK8sServiceClient.get_metadata requires "
"worker_rank; call publish_metadata first or set it "
"at construction time"
)
endpoint = self._resolve_endpoint()
last_error: Exception | None = None
for attempt in range(1, self._max_retries + 2):
channel = grpc.insecure_channel(endpoint)
try:
stub = p2p_pb2_grpc.WorkerServiceStub(channel)
req = p2p_pb2.GetTensorManifestRequest(mx_source_id=mx_source_id)
resp = stub.GetTensorManifest(req, timeout=30)
mismatch_reason: str | None = None
if resp.mx_source_id != mx_source_id:
mismatch_reason = (
f"mx_source_id mismatch: expected "
f"{mx_source_id!r}, got {resp.mx_source_id!r}"
)
elif resp.worker_rank != self._worker_rank:
mismatch_reason = (
f"worker_rank mismatch: expected "
f"{self._worker_rank}, got {resp.worker_rank}"
)
if mismatch_reason is not None:
last_error = RuntimeError(
f"manifest from {endpoint} failed validation: "
f"{mismatch_reason}"
)
if attempt <= self._max_retries:
logger.warning(
"MxK8sServiceClient.get_metadata: "
"%s on attempt %d/%d; retrying on fresh "
"channel after %.2fs backoff",
mismatch_reason, attempt,
self._max_retries + 1, self._backoff_seconds,
)
time.sleep(self._backoff_seconds)
continue
raise last_error
worker = p2p_pb2.WorkerMetadata(
worker_rank=resp.worker_rank,
metadata_endpoint=resp.metadata_endpoint,
agent_name=resp.agent_name,
tensor_source=tensor_source_metadata(resp.tensors),
status=p2p_pb2.SOURCE_STATUS_READY,
worker_grpc_endpoint=endpoint,
accelerator=resp.accelerator,
)
logger.info(
"MxK8sServiceClient.get_metadata: fetched "
"manifest from %s (mx_source_id=%s, rank=%d, "
"%d tensors, attempt=%d)",
endpoint, resp.mx_source_id, resp.worker_rank,
len(resp.tensors), attempt,
)
return p2p_pb2.GetMetadataResponse(
found=True,
worker=worker,
mx_source_id=resp.mx_source_id,
worker_id=worker_id,
)
except grpc.RpcError as exc:
last_error = exc
if (
exc.code() == grpc.StatusCode.FAILED_PRECONDITION
and attempt <= self._max_retries
):
logger.warning(
"MxK8sServiceClient.get_metadata: "
"mx_source_id mismatch on attempt %d/%d "
"against %s (server: %s); retrying on fresh "
"channel after %.2fs backoff",
attempt, self._max_retries + 1, endpoint,
exc.details(), self._backoff_seconds,
)
time.sleep(self._backoff_seconds)
continue
raise
finally:
channel.close()
message = (
f"MxK8sServiceClient.get_metadata: exhausted "
f"{self._max_retries + 1} attempts against {endpoint}"
)
logger.error("%s: %s", message, last_error)
raise RuntimeError(f"{message}: {last_error}") from last_error
def update_status(
self,
mx_source_id: str,
worker_id: str,
worker_rank: int,
status: "p2p_pb2.SourceStatus",
source_load: float = 0.0,
) -> bool:
return True
def _resolve_endpoint(self) -> str:
resolved = self._service_pattern.format(rank=self._worker_rank)
if ":" in resolved:
return resolved
base_port = envs.MX_WORKER_GRPC_PORT
return f"{resolved}:{base_port + self._worker_rank}"