Repository navigation
fix(protocol): keep model identity groups when a withdrawn llm service is restored - #72
Open
JeniaJitsev wants to merge 2 commits into
Open
JeniaJitsev wants to merge 2 commits into
JeniaJitsev wants to merge 2 commits into
Conversation
…e is restored
registerLLMService takes an llm worker's identity groups from /v1/models
("model=<id>"); they are not in the configuration. When the health
revalidation loop restored a withdrawn service, it re-advertised it with
service.identity_group alone, so the worker came back ready but without
model groups, and model-based routing never reached it again until the
worker was restarted.
On restore, an llm service now reads /v1/models again (one attempt; it
just passed a health check). If that fails, the identity groups the
service held when it was withdrawn are used. Configured groups are kept
in both cases. The model fetch is factored out of registerLLMService
into fetchLLMIdentityGroups, unchanged in behaviour there.
Assisted by Claude Opus 5.5
… healthy Restoring a withdrawn llm service read /v1/models once and never again. If that read failed, the worker kept the groups it held at withdrawal, which can name a model it no longer serves, or none at all. A model swap too quick to trigger a withdrawal, or a failed read at startup, was also never corrected. Each healthy tick now re-reads the model list and publishes on change. On a failed read the current groups are kept. Also: - An empty model list is a failed read, so startup retries it during warm-up and logs the final failure instead of registering silently without groups. - Restore replaces the local entry instead of merging into it, so a model re-added through POST /v1/dnt/_node while withdrawn does not outlive a successful read. Refresh and restore replace only model=<id> groups; other groups and model=* are kept. - The service stays withdrawn until its new entry is in place, so a reannounce during the /v1/models read cannot publish the old groups. - service.identity_group no longer applies to an llm service on restore. It never applied at startup, so a flap changed the worker's routing identity. - removeLocalService returns the groups it removed, and the loop body moves to serviceRevalidator.tick so the withdraw and restore wiring is tested against a real HTTP backend. This replaces llmIdentityGroupsFetcher and localServiceIdentityGroups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RijVob4d6Y1reSaEVvNJJU Entire-Checkpoint: 01M6S03AHVPTYZZZ65K59W06WA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
registerLLMServicebuilds an llm worker's identity groups from the backend's/v1/models(model=<id>). They are not part of the configuration. WhenStartServiceHealthRevalidationrestores a service it had withdrawn, it re-advertises it withviper.GetStringSlice("service.identity_group")only. For a typical llm worker that list is empty. The worker comes backreadybut carries no model groups, so model-based routing does not reach it, and nothing repairs that short of restarting the worker.Observed
On 2026-11-06, on a private mesh at f740781, two workers fronted vLLM engines through an SSH-tunnelled loopback bridge. Both failed three consecutive health probes during a stretch of slow logins on the remote side, and both were withdrawn ("failed 3 consecutive health checks; withdrawing advertisement"). Once the backends answered again, the head's
/v1/dnt/tablelisted both workersreadywith anllmservice whoseidentity_groupwas empty. The gateway's model list no longer contained their models. Restarting only the worker processes (backends untouched) re-registered them withmodel=<id>groups within about 30 s, and inference worked again.Fix
registerLLMServiceintofetchLLMIdentityGroups(port, attempts, interval). Behaviour there is unchanged: 10 attempts, 3 s apart, loud on final failure.restoredServicere-reads/v1/modelsonce for anllmservice (it has just passed a health check). If that read fails, it uses the identity groups the service held when it was withdrawn, recorded by the loop beforeremoveLocalService. Configured groups are kept in both cases. Non-llm services behave as before.llmIdentityGroupsFetcher) so the restore path can be tested without a network.Tests
go test ./internal/protocol/...andgo vet ./internal/protocol/pass with Go 1.27.3, andgo build ./...succeeds. The baseline on upstream main (4b6e90f) passed before the change. New tests:TestRestoredService: llm restore uses the fetched groups; configured groups are merged; a failed fetch falls back to the groups held at withdrawal; a non-llm service keeps its configured groups and is never probed.TestFetchLLMIdentityGroups: parses/v1/modelsfrom an httptest server and returns an error once it is gone.TestLocalServiceIdentityGroups: returns a copy, or nil for an unknown service.Separate observation, not addressed here
After a worker restart, the head sometimes did not pick up the worker's node-table row for several minutes. The worker listed itself
readywith its services, while the head's/v1/dnt/tablelacked it, and stopping the worker for 20 s or 70 s before starting it did not help. APOST /v1/dnt/_nodewith body{}on the worker, which forcesUpdateNodeTableand a freshstore.Put, made the row appear at the head within 5 s. I have not traced the cause; it may deserve its own issue.🤖 Generated with Claude Code